1. 程式人生 > >Git程式碼合併Merge還是Rebase

Git程式碼合併Merge還是Rebase

git rebase 這個命令經常被人認為是一種Git巫術,初學者應該避而遠之。但如果使用得當的話,它能給你的團隊開發省去太多煩惱。在這篇文章中,我們會比較 git rebase 和類似的 git merge 命令,找到Git工作流中rebase的所有用法。

概述

你要知道的第一件事是, git rebase 和 git merge 做的事其實是一樣的。它們都被設計來將一個分支的更改併入另一個分支,只不過方式有些不同。

想象一下,你剛建立了一個專門的分支開發新功能,然後團隊中另一個成員在master分支上添加了新的提交。這就會造成提交歷史被Fork一份,用Git來協作的開發者應該都很清楚。

現在,如果master中新的提交和你的工作是相關的。為了將新的提交併入你的分支,你有兩個選擇:merge或rebase。

Merge

將master分支合併到feature分支最簡單的辦法就是用下面這些命令:

git checkout feature
git merge master

或者,你也可以把它們壓縮在一行裡。

git merge master feature

feature分支中新的合併提交(merge commit)將兩個分支的歷史連在了一起。你會得到下面這樣的分支結構:

Merge好在它是一個安全的操作。現有的分支不會被更改,避免了rebase潛在的缺點(後面會說)。

另一方面,這同樣意味著每次合併上游更改時feature分支都會引入一個外來的合併提交。如果master非常活躍的話,這或多或少會汙染你的分支歷史。雖然高階的 git log 選項可以減輕這個問題,但對於開發者來說,還是會增加理解專案歷史的難度。

Rebase

作為merge的替代選擇,你可以像下面這樣將feature分支併入master分支:

git checkout feature
git rebase master

它會把整個feature分支移動到master分支的後面,有效地把所有master分支上新的提交併入過來。但是,rebase為原分支上每一個提交建立一個新的提交,重寫了專案歷史,並且不會帶來合併提交。

rebase最大的好處是你的專案歷史會非常整潔。首先,它不像 git merge 那樣引入不必要的合併提交。其次,如上圖所示,rebase導致最後的專案歷史呈現出完美的線性——你可以從專案終點到起點瀏覽而不需要任何的Fork。這讓你更容易使用 git log 、 git bisect 和 gitk 來檢視專案歷史。

不過,這種簡單的提交歷史會帶來兩個後果:安全性和可跟蹤性。如果你違反了Rebase黃金法則,重寫專案歷史可能會給你的協作工作流帶來災難性的影響。此外,rebase不會有合併提交中附帶的資訊——你看不到feature分支中併入了上游的哪些更改。

互動式的rebase

互動式的rebase允許你更改併入新分支的提交。這比自動的rebase更加強大,因為它提供了對分支上提交歷史完整的控制。一般來說,這被用於將feature分支併入master分支之前,清理混亂的歷史。

把 -i 傳入 git rebase 選項來開始一個互動式的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,專案歷史看上去會是這樣:

忽略不重要的提交會讓你的feature分支的歷史更清晰易讀。這是 git merge 做不到的。

Rebase的黃金法則

當你理解rebase是什麼的時候,最重要的就是什麼時候 不能 用rebase。 git rebase 的黃金法則便是,絕不要在公共的分支上使用它。

比如說,如果你把master分支rebase到你的feature分支上會發生什麼:

這次rebase將master分支上的所有提交都移到了feature分支後面。問題是它只發生在你的程式碼倉庫中,其他所有的開發者還在原來的master上工作。因為rebase引起了新的提交,Git會認為你的master分支和其他人的master已經分叉了。

同步兩個master分支的唯一辦法是把它們merge到一起,導致一個額外的合併提交和兩堆包含同樣更改的提交。不用說,這會讓人非常困惑。

所以,在你執行 git rebase 之前,一定要問問你自己“有沒有別人正在這個分支上工作?”。如果答案是肯定的,那麼把你的爪子放回去,重新找到一個無害的方式(如 git revert )來提交你的更改。不然的話,你可以隨心所欲地重寫歷史。

強制推送

如果你想把rebase之後的master分支推送到遠端倉庫,Git會阻止你這麼做,因為兩個分支包含衝突。但你可以傳入 --force 標記來強行推送。就像下面一樣:

# 小心使用這個命令!
git push --force

它會重寫遠端的master分支來匹配你倉庫中rebase之後的master分支,對於團隊中其他成員來說這看上去很詭異。所以,務必小心這個命令,只有當你知道你在做什麼的時候再使用。

僅有的幾個強制推送的使用場景之一是,當你在想向遠端倉庫推送了一個私有分支之後,執行了一個本地的清理(比如說為了回滾)。這就像是在說“哦,其實我並不想推送之前那個feature分支的。用我現在的版本替換掉吧。”同樣,你要注意沒有別人正在這個feature分支上工作。

工作流

rebase可以或多或少應用在你們團隊的Git工作流中。在這一節中,我們來看看在feature分支開發的各個階段中,rebase有哪些好處。

第一步是在任何和 git rebase 有關的工作流中為每一個feature專門建立一個分支。它會給你帶來安全使用rebase的分支結構:

本地清理

在你工作流中使用rebase最好的用法之一就是清理本地正在開發的分支。隔一段時間執行一次互動式rebase,你可以保證你feature分支中的每一個提交都是專注和有意義的。你在寫程式碼時不用擔心造成孤立的提交——因為你後面一定能修復。

呼叫 git rebase 的時候,你有兩個基(base)可以選擇:上游分支(比如master)或者你feature分支中早先的一個提交。我們在“互動式rebase”一節看到了第一種的例子。後一種在當你只需要修改最新幾次提交時也很有用。比如說,下面的命令對最新的3次提交進行了互動式rebase:

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

互動式rebase是在你工作流中引入 git rebase 的的好辦法,因為它隻影響本地分支。其他開發者只能看到你已經完成的結果,那就是一個非常整潔、易於追蹤的分支歷史。

但同樣的,這隻能用在私有分支上。如果你在同一個feature分支和其他開發者合作的話,這個分支是公開的,你不能重寫這個歷史。

用帶有互動式的rebase清理本地提交,這是無法用 git merge 命令代替的。

將上游分支上的更改併入feature分支

在概覽一節,我們看到了feature分支如何通過 git merge 或 git rebase 來併入上游分支。merge是保留你完整歷史的安全選擇,rebase將你的feature分支移動到master分支後面,建立一個線性的歷史。

git rebase 的用法和本地清理非常類似(而且可以同時使用),但之間併入了master上的上游更改。

記住,rebase到遠端分支而不是master也是完全合法的。當你和另一個開發者在同一個feature分之上協作的時候,你會用到這個用法,將他們的更改併入你的專案。

比如說,如果你和另一個開發者——John——往feature分支上添加了幾個提交,在從John的倉庫中fetch之後,你的倉庫可能會像下面這樣:

就和併入master上的上游更改一樣,你可以這樣解決這個Fork:要麼merge你的本地分支和John的分支,要不把你的本地分支rebase到John的分支後面。

注意,這裡的rebase沒有違反Rebase黃金法則,因為只有你的本地分支上的commit被移動了,之前的所有東西都沒有變。這就像是在說“把我的改動加到John的後面去”。在大多數情況下,這比通過合併提交來同步遠端分支更符合直覺。

預設情況下, git pull 命令會執行一次merge,但你可以傳入 --rebase 來強制它通過rebase來整合遠端分支。

用Pull Request進行審查

如果你將pull request作為你程式碼審查過程中的一環,你需要避免在建立pull request之後使用 git rebase 。只要你發起了pull request,其他開發者能看到你的程式碼,也就是說這個分支變成了公共分支。重寫歷史會造成Git和你的同事難以找到這個分支接下來的任何提交。

來自其他開發者的任何更改都應該用 git merge 而不是 git rebase 來併入。

因此,在提交pull request前用互動式的rebase進行程式碼清理通常是一個好的做法。

併入通過的功能分支

如果某個功能被你們團隊通過了,你可以選擇將這個分支rebase到master分支之後,或是使用 git merge 來將這個功能併入主程式碼庫中。

這和將上游改動併入feature分支很相似,但是你不可以在master分支重寫提交,你最後需要用 git merge 來併入這個feature。但是,在merge之前執行一次rebase,你可以確保merge是一直向前的,最後生成的是一個完全線性的提交歷史。這樣你還可以加入pull request之後的提交。

如果你還沒有完全熟悉 git rebase ,你還可以在一個臨時分支中執行rebase。這樣的話,如果你意外地弄亂了你feature分支的歷史,你還可以檢視原來的分支然後重試。

比如說:

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [清理目錄]
git checkout master
git merge temporary-branch

總結

你使用rebase之前需要知道的知識點都在這了。如果你想要一個乾淨的、線性的提交歷史,沒有不必要的合併提交,你應該使用 git rebase 而不是 git merge 來併入其他分支上的更改。

另一方面,如果你想要儲存專案完整的歷史,並且避免重寫公共分支上的commit, 你可以使用 git merge 。兩種選項都很好用,但至少你現在多了 git rebase 這個選擇。