雖然 merging 和 rebasing 在 git 中相似時,但他們提供不同的功能。為了讓你的歷史盡可能的干凈和完整,你應該知道以下幾點。
git rebase
命令已 神奇的 Git voodoo 而聞名,初學者應該遠離它,但它實際上可以讓開發團隊在使用時更加輕松。在本章中,我們將 把 git rebase
和與之有關聯的 git merge
命令相比較 ,並在典型的 Git 工作流 中重新定位,識別其所有潛在的機會。
概述
首先要明白關於 git rebase
的事情是它像 git merge
一樣解決相同的問題。git rebase 和 git merge 一樣都是被設計用於從一個分支獲取並合並到當前分支,但是他們采取不同的工作方式。
考慮一下,當你開始在 一個專用的分支上開發新特性,與此同時另一個團隊成員用新的提交來更新了 master
分支時,會發生什么呢?這會導致分叉的歷史記錄,對於這個問題,使用 Git 作為協同工具的任何人來說都應該很熟悉。
現在,假設你在工作時在 master
上的新提交與新特性相關。為了將新提交合並到你的 feature
分支上,你有兩種選擇:merging 或者 rebasing。
Merge 選項
最簡單的選項是使用以下命令將 master
分支合並到 feature
分支:
git checkout feature
git merge master
或者,你可以簡化成一句:
git merge master feature
這將在 feature
分支上創建一個新 “ 合並提交 ” ,並把兩個分支的歷史聯系在一起。分支結構顯示如下:
Merging 之所以好是因為它是一個不可逆的操作。在任何情況下,現有分支不能被更改。這避免了所有 rebasing 的潛在陷阱(詳見下文)。
另一方面,這也意味着每次需要合並上游更改時, feature
分支都將有一個額外的 merge 提交產生。如果 master
非常活躍,這可能破壞你全部的 feature 分支的歷史。使用高級的 git log
選項來減緩這個問題是有可能的,也讓其他開發人員很難理解這個項目的歷史記錄。
Rebase 選項
作為 merging 的一個替代品,你可以使用以下命令將 feature
分支合並到 master
分支:
git checkout feature
git rebase master
這將整個 feature
分支從 master
分支的頂端開始,有效地將所有新的提交合並到主分支中。但是,並不是使用合並提交,而是通過為每個在原始分支上的提交創建全新的提交來重寫項目歷史。
rebasing 最主要的益處是你將獲得一個十分干凈整潔的項目歷史。首先,它通過 git merge
排除多余的 merge 提交需求;其次,正如你在上圖所看到的那樣,rebasing 也會產生完美線性的項目歷史記錄—你可以順着 feature
一直到項目的起始位置而沒有任何分支。可以方便的使用 git log
,git bisect
和 gitk
追蹤提交記錄。
但是,對於新的提交歷史有兩點需要權衡:安全性和可追溯性。如果你不遵循 Rebasing 的黃金法則,為你的協作工作流重寫項目歷史可能會成為潛在的災難。另外,不重要的是,rebasing 會丟失合並提交所提供的上下文—你不能看到何時合並到 feature 分支中的上游變化。
交互式的 Rebasing
當他們移動到新的分支上,交互式合並給你機會來修改提交。自從它提供完全控制整個分支的提交歷史之后,它比自動合並更強大。具有代表性的,在合並一個 feature 分支到 master
時,它是被用來清除錯誤的歷史。
要開始一個交互式的重基會話,請將 i
選項傳遞給 git rebase
命令:
git checkout feature
git rebase -i master
這將打開一個文本編輯器列出所有要被移動的提交:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
此列表准確定義了執行 rebase 后分支的外觀。通過改變 pick
命令或調整條目順序來改變分支的提交歷史,你可以讓分支看起來像任何你想要的樣子。舉例說,如果第二次提交是為了修復第一次提交中的一個小問題,你可以使用 fixup
命令把他們簡化成一個簡單的命令:
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
當你保存並關閉文件時,Git 將根據你的指令來執行 rebase ,從而產生如下所示的項目歷史記錄:
像這樣排除不重要的提交使你的特性歷史相當易懂。這一點是 git merge
無法比擬的。
Rebasing 的黃金規則
一旦你明白什么是 rebasing ,最重要的事情是學習什么時候不用它。 git rebase
的黃金法則是永遠不要在公有分支上使用它。
舉例說,想象一下如果你將 master
分支合並到 feature
分支上會發生什么:
rebase 操作將 master
中所有提交移動到 feature
的頭部,但問題是這一切都發生在你的倉庫中。其他開發者依然在原來的 master
分支上繼續工作。自從 rebasing 產生了全新的提交,Git 將會認為你的 master
分支的歷史記錄與其他人的歷史記錄不同。
使兩個 master
分支 同步的唯一方法是將他們合並到一起,導致出現一個額外的合並操作和兩組都包含相同改變(最原始的那個,和那些來自你重新建立的分支)的提交。不用說,這是一個非常混亂的場景。
因此,在你運行 git rebase
之前,一定要問自己,“還有其他人在看這個分支嗎?”,如果回答是肯定的,那么把你的手從鍵盤上拿開並開始考慮讓你的改變沒有破壞性(例如, git revert
命令)。否則,你可以隨心所欲地重寫歷史。
Force-Pushing
如果你嘗試將合並的 master
分支推送到遠程庫中,Git 將防止你這樣做,因為它與遠程 master
分支有沖突。但是,你可以通過傳遞 --force
標志來強制推送,就像這樣:
# Be very careful with this command!
git push --force
該操作會將遠程倉庫的 master
分支替換為 rebase 過的 master
分支,這會給團隊的其他成員帶來困擾。因此,當你確切的知道你要做什么的時候,才要非常小心的使用這些命令。
推送一個私有新特性分支到遠程倉庫(例如,用於備份)。這就好像是說,“哎呦,我不想推送 feature 分支的原始版本,拿當前的版本替換吧。”再強調一次,沒有人在 feature 分支的原始版本中工作是很重要的。
工作流演練
Rebasing 能夠根據團隊的需要或多或少的被合並到你現存的 Git 工作流 中。在這個選項中,我們將檢查 rebasing 提供在不同階段的 feature 分支開發的好處。
在任何工作流中,首先第一步是利用 git rebase
為每一個 feature 創建一個專用的分支。這給你必要分支結構來安全使用 rebasing :
本地清除
最好的方法之一是合並 rebasing 到你的 工作流 以此來清理本地正在進行的 feature 分支。通過定期的執行一個交互式的 rebase ,你可以確保每一個在你的 feature 分支中的提交是集中且有意義的。這將讓你編寫你自己的代碼而不需要在獨立提交中擔心破壞它—你可以在事后修復它。
當調用 git rebase
,對於新的分支你有兩個選項:feature 父類分支(舉例說,master
分支),或者在你的 feature 分支中較早的提交。我們查看了在 交互式的 Rebasing 章節中首個選項的示例 。當你僅僅需要修復最新提交時,后者的選擇最好。舉例說,交互式 rebase 的最后3次提交顯示如下:
git checkout feature
git rebase -i HEAD~3
通過指定 HEAD~3
作為新的基礎,事實上你並沒有移動分支—你只是交互式的重寫了接下來的3次提交。請注意,這不會將上游更改合並到 feature
分支。
如果你想使用這個方法重寫整個 feature, git merge-base
命令對於找到 feature
分支的原始起始點非常有用。以下返回原始起始點的提交 ID ,然后傳遞給 git rebase
:
git merge-base feature master
交互式 rebasing 的作用在於當他僅僅影響本地分支時,它是一個 引進 git rebase
到工作流中的好方式。其他開發人員唯一能看到的是你最后提交的成果,這應該是一個簡單且易於理解的 feature 分支歷史記錄。
但是在剛開始,這僅僅只為私有 feature 分支工作。如果你借助相同 feature 分支與其他開發者協作,分支是共有的,你也不被允許重寫它的歷史記錄。
沒有 git merge
之外的其他選擇時可以使用交互式 rebase 來清除本地提交。
合並上游更改到 Feature 中
在開篇章節中,我們知道了 feature 分支如何使用 git merge
或 git rebase
合並 master
分支的上游提交。當 rebasing 通過移動你的 feature 分支到 master
分支的頭部來創建一個線性歷史時,Merging 是一個用於保護你倉庫的整個歷史記錄的安全選項。
git rebase
的作用與本地清除相似(能夠同時被執行),但是在此過程中,它合並了 master
的上游提交。
牢記,遠程分支取代 master
分支是完全合法的。這發生在其他開發者在同一個 feature 分支上協作時和你需要合並他們的更改到你的倉庫中時。
舉例說明,如果你和一個名為 John 的開發人員添加了對 feature
分支的提交,從 John 的倉庫中獲取遠程 feature
分支后,你的倉庫看起來像如下所示:
你可以用與 master
分支集成上游更改相同的方法來解決這個分叉:或者你本地的 feature
分支與 john/feature
分支合並,或者 rebase 你本地 feature
分支到 john/feature
分支的頭部。
請注意,任何事情在未更改之前,rebase 不能違反 Rebasing 的黃金法則 ,因為 feature
僅僅移動了本地提交。這就好像是在說,“將我的更改添加到 John 已經完成了的操作中。” 在大多數情況下,這比通過合並提交與遠程分支同步更為直觀。
默認情況下, git pull
命令執行合並,但是你可以強制通過使用 rebase 的 --rebase
選項整合遠程分支。
使用 Pull 請求檢驗 feature 分支
如果你使用 Pull 請求作為代碼的審計過程,創建的 pull 請求之后,你需要避免使用 git rebase
。一旦你發出 pull 請求,其他開發人員就能看到你的提交,這就意味着它是一個公有分支。重寫它的歷史記錄將使 Git 和你的隊友無法追蹤到任何添加到 feature 分支上的后續提交。
任何來自其他開發者的更改需要使用 git merge
取代 git rebase
來被合並。
為此,在提交你的 pull 請求之前,使用交互式 rebase 清理你的代碼,通常是一個好主意。
整合認可的 feature
在 feature 分支被你的團隊認可之后,在使用 git merge
整合 feature 分支到主代碼庫之前,你有一個 rebasing feature 分支到 master
分支的選項。
合並上游更改到 feature 分支是一個類似的情況,但是,自從你不被允許在 master
中重寫提交,你最后不得不使用 git merge
來整合 feature 分支。然而,通過在合並之前執行 rebase 確保 merge 將快速進行,形成完美的線性歷史。這也給了你在 pull 請求期間將任何后續提交塞入到 feature 分支中的機會。
如果你對 git rebase
感到不太舒服,你可以在臨時分支中一直執行 rebase。那樣,如果你一不小心搞砸了你的 feature 分支歷史記錄,你可以多次檢查原始分支。例如:
git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch
總結
在你開始 rebasing 你的分支之前,這是所有你真正需要知道:如果您想要一個沒有不必要的干凈的合並提交的線性歷史記錄,你應該爭取 git rebase
代替 git merge
整合來自另一個分支的改變。
另一方面,如果你想保存你項目的完整歷史並且避免重寫公有提交的風險,你可以堅持使用 git merge
。任何一個選項都是完全有效的,至少現在你是有選擇性的利用 git rebase
的好處。
本文作者:Tim Pettersen, 翻譯:Queena
原文鏈接:https://dzone.com/articles/merging-vs-rebasing
譯文首發:http://didispace.com/git-merge-rebase-compare/
本文有spring4all技術翻譯組完成,更多國外前沿知識和干貨好文,歡迎關注公眾號:后端面試那些事兒。