用Git進行多人協作開發時,必然會合並代碼,解決沖突。然而合並代碼也是需要點技巧的,如果對一些關鍵命令沒有理解去使用的話,git的版本演進路線就會變得很亂,從而造成了日后維護的一些麻煩。
Git上合並代碼有git merge 以及 git rebase 兩種方式。下面將深入兩者的用法以及對兩者的適用場景作個總結。
前置知識點
- Master分支:首先,代碼庫應該有一個、且僅有一個主分支。所有提供給用戶使用的正式版本,都在這個主分支上發布。這個分支被稱為Master分支;
- Develop分支:主分支只用來分布重大版本,日常開發應該在另一條分支上完成。我們把開發用的分支,叫做Develop分支。這個分支可以用來生成代碼的最新隔夜版本(nightly)。如果想正式對外發布,就在Master分支上,對Develop分支進行"合並"(merge)。
- 臨時性分支:除了常設分支以外,還有一些臨時性分支,用於應對一些特定目的的版本開發。臨時性分支主要有三種:
- 功能(feature)分支:它是為了開發某種特定功能,從Develop分支上面分出來的。開發完成后,要再並入Develop。它的命名,可以采用feature-*的形式。
- 預發布(release)分支:它是指發布正式版本之前(即合並到Master分支之前),我們可能需要有一個預發布的版本進行測試。預發布分支是從Develop分支上面分出來的,預發布結束以后,必須合並進Develop和Master分支。它的命名,可以采用release-*的形式。
- 修補bug分支:軟件正式發布以后,難免會出現bug。這時就需要創建一個分支,進行bug修補。修補bug分支是從Master分支上面分出來的。修補結束以后,再合並進Master和Develop分支。它的命名,可以采用fixbug-*的形式。
有了以上知識點,我們可以了解一般團隊開發都是基於feature分支進行開發,然后把feature分支合並到develop分支的。接着我們模擬如下一個實際開發場景。
場景
現在在develop開發分支上,然后你創建了一個feature分支開發新功能,現在團隊中另一個成員在develop分支上添加了新的提交。如下圖所示
現在,如果develop中新的提交和你的工作是相關的。為了將新的提交並入你的分支,你有兩個選擇:merge或rebase。
merge
git merge
將develop分支合並到feature分支最簡單的辦法就是用下面這些命令:
git checkout feature
git merge develop
或者,你也可以把它們壓縮在一行里。(個人還是喜歡上面的寫法)
git merge develop feature
feature分支中新的合並提交(merge commit)將兩個分支的歷史連在了一起。你會得到下面這樣的分支結構:
Merge好在它是一個安全的操作。現有的分支不會被更改,避免了rebase潛在的缺點(后文會講)。但是這同樣意味着每次合並上游更改時feature分支都會引入一個外來的合並提交。如果master非常活躍的話,這或多或少會污染你的分支歷史。
git merge --no-ff
默認情況下,Git執行"快進式合並"(fast-farward merge),會直接將develop分支指向feature分支。如git merge里的圖所示。使用--no-ff參數后,會執行正常合並,在develop分支上生成一個新節點。為了保證版本演進的清晰,我們希望采用這種做法。關於合並的更多解釋,請參考Benjamin Sandofsky的《Understanding the Git Workflow》。
Git演進圖如下圖所示。(如有錯誤歡迎指正)
可以看到,使用了git merge --no-ff 命令后的git 演進路線是清晰的,命令概括如下:
git checkout feature
git merge --no-ff develop
----------------- 分割線 -------------------
git rebase
先提個問題吧,git rebase 和 git reset 有什么區別? 如果不知道的話,可以在回顧一下在什么場景下用git merge以及git rebase的,而git reset則僅僅是在當前的分支(一個分支)的版本切換。
接着來講git rebase。作為merge的替代選擇,你可以像下面這樣將feature分支並入master分支:
git checkout feature
git rebase develop
它會把整個feature分支移動到develop分支的后面,有效地把所有develop分支上新的提交並入過來。但是,rebase為原分支上每一個提交創建一個新的提交,重寫了項目歷史,並且不會帶來合並提交。
rebase最大的好處是你的項目歷史會非常整潔。首先,它不像git merge
那樣引入不必要的合並提交。其次,如上圖所示,rebase導致最后的項目歷史呈現出完美的線性。這讓你更容易使用git log來查看項目歷史。
不過,這種簡單的提交歷史會帶來兩個后果:安全性和可跟蹤性。如果你違反了Rebase黃金法則,重寫項目歷史可能會給你的協作工作流帶來災難性的影響。此外,rebase不會有合並提交中附帶的信息——你看不到feature分支中並入了上游的哪些更改。
> git merge --no-ff 在每次合並都會產生一個新的合並記錄; git merge 的話只有解決沖突的時候才會產生一個新的合並記錄。
rebase的黃金法則
當你理解rebase是什么的時候,最重要的就是什么時候 不能 用rebase。git rebase
的黃金法則便是,絕不要在公共的分支上使用它。
比如說,如果你在develop分支上,rebase到你的feature分支上會發生什么:
這次rebase將develop分支上的所有提交都移到了feature分支后面。問題是它只發生在你的代碼倉庫中,其他所有的開發者還在原來的develop上工作。因為rebase引起了新的提交,Git會認為你的develop分支和其他人的develop已經分叉了。
同步兩個develop分支的唯一辦法是把它們merge到一起,導致一個額外的合並提交和兩堆包含同樣更改的提交。不用說,這會讓人非常困惑。
所以重要的再強調一遍,絕不要在公共的分支上使用它。在你運行git rebase
之前,一定要問問你自己“有沒有別人正在這個分支上工作?”。如果答案是肯定的,重新找到一個無害的方式(如git revert
)來提交你的更改。不然的話,你可以隨心所欲地重寫歷史。
總結
如果你想要一個干凈的、線性的提交歷史,沒有不必要的合並提交,你應該使用git rebase
而不是git merge
來並入其他分支上的更改。
另一方面,如果你想要保存項目完整的歷史,並且避免重寫公共分支上的commit, 你可以使用git merge (--no-ff)。
參考文獻: