導語:作者闡釋了 git rebase 命令的原理和缺陷,rebase 會導致線性的歷史沒有分支。此外如果 rebase 過程中發生了衝突,還可能會引入更多的問題。作者推薦使用 git merge。
用了幾年的 Git 後 ,我發現使用越來越多的先進的 Git 命令已經逐漸成為我日常工作流的一部分了。在我發現 Git rebase 後,我馬上就把它收錄到我日常工作流中。熟悉 rebase 的人都知道它的強大之處,並且它總是在誘惑別人使用它。然而,我很快就發現了在開始使用 rebase 後帶來的一些不是很明顯的挑戰。在闡述挑戰之前,我將快速地講述一下 merge 和 rebase 之間的差別。
我們首先來思考一下將 feature 分支合併到 master 分支的基礎案例。通過 merge 的話,我們建立了一個新的提交g
表示兩個分支的合併。提交圖清晰的展現了發生了什麼,我們可以從更大的 Git-repos 中看到熟悉的“火車軌道”輪廓。
Example of merging
複製程式碼
同樣地,我們可以在 merge 之前選擇 rebase。提交會被移除,並且 feature 分支被重置到 master 分支,feature 分支上的提交被重新應用到 master。差別在於這些重新應用的提交通常是原始的副本,它們的 SHA-1 金鑰和原來的提交不一樣。
Example of rebasing
複製程式碼
我們現在將 feature 的基礎提交從b
變為了c
,這就是 rebase 的意思。將 feature 合併到 master 是一個快進合併,因為在 feature 上的所有提交都是 master 的直接子代。
Example of fast-forward merging
複製程式碼
和 merge 的方法比較起來,rebase 導致分支的歷史都是線性的。我以前更喜歡在合併之前 rebase 分支的原因在於提高了可讀性,我認為其他的開發者應該也是這種情況。
但是,這種方式帶來了一些不是很明顯的挑戰。
考慮一下這種情況,有一個依賴在 master 上被移除了,但在 feature 上還在使用。當 feature 分支 rebase 到 master 上時,第一個重新應用的提交會打破你的構建,但只要沒有合併衝突,rebase 就不會被中斷。從第一個提交出現的錯誤會保留在隨後的所有提交中,這導致了一個鏈式的錯誤提交。
這個錯誤只會在 rebase 完成後才會被發現,並且通常會在頂部增加一個修復 bug 的提交g
。
Example of failed rebasing
複製程式碼
如果你在 rebase 過程中出現了衝突,Git 將會暫停在衝突的提交上,允許你在開始之前解決衝突。在一系列的提交中間解決衝突通常會讓人困惑,難以改正,並且可能會導致額外的錯誤。
引入錯誤是在 rebase 過程中發生的。這樣,當你重寫歷史的時候,新的錯誤就會被引入,它們可能會掩蓋第一次寫入歷史時造成的真正的錯誤。尤其是,當我們使用 Git bisect 時會變得更加困難,Git bisect 可以說是 Git 工具箱中最強大的除錯工具了。例如,思考一下下面的 feature 分支,我們假設在分支的末端引入了一個錯誤。
A branch with bugs introduced towards the end
複製程式碼
為了找到引入錯誤的提交,你可能會搜尋幾十個甚至上百個提交。這個過程可以通過編寫測試錯誤存在的指令碼來自動執行,並通過 Git bisect 使用命令git bisect run <yourtest.sh>
來執行。
Bisect 會通過二分查詢整個歷史,識別出引入 bug 的提交。在上面展示的案例中,它成功地找到第一個錯誤的提交,因為所有的有問題的提交包含著我們正在尋找的真正的錯誤。
Example of successfull Git bisect
複製程式碼
另一方面,如果我們在 rebase 過程中引入了額外的錯誤提交(下圖的d
和e
),bisect 將會遇到麻煩。這個例子中,我們希望 Git 識別出f
提交,但它會錯誤地識別出d
,因為它包含了其他的錯誤打破了測試。
Example of a failed Git bisect
複製程式碼
這個問題比看起來更大。
我們為什麼要使用 Git?
因為它是我們追蹤我們程式碼中錯誤來源最重要的工具。Git 是我們的安全網。通過 rebase 雖然能夠達成線型歷史,但我們會給予較少的優先權。
回顧一下,我必須使用 bisect 追蹤系統中上百個提交。這個錯誤的提交在一條未編譯的提交鏈中間,因為一個錯誤的 rebase。這個不必要的並且今天可以避免的錯誤導致我花了一天的時間來追蹤提交。
所以,我們怎樣才能避免在 rebase 過程中出現錯誤的提交鏈呢?一個方法是在 rebase 結束後,測試程式碼來發現錯誤,然後回到我們引進錯誤的地方修復它。另一個方法是,在 rebase 過程中暫停每一個步驟,在繼續處理之前測試並修復它。
這是一個笨重的,並且容易犯錯的過程。這麼做的唯一結果是獲得一個線型的歷史。還有更簡單,更好的方式嗎?
答案是:Git merge。它是一個簡單的,一步到位的過程,所有的衝突都可以在一個單一的提交中解決。合併的提交清晰地顯示了我們的分支之間的互動點,並且我們的歷史敘述了實際上發生的和什麼時候發生的。
綜上所述,推薦使用 merge 而不是 rebase。保持我們歷史的真實性是不可低估的。至於人們為什麼要使用 rebase,可能是出於虛榮心,科科。
- 譯者:KenChoi
- 原文連結:Why you should stop using Git rebase
- 知乎專欄:極光日報