Merging 和 Rebasing 的大比拼

雖然 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 分支上創建一個新 “ 合併提交 ” ,並把兩個分支的歷史聯繫在一起。分支結構顯示如下:

img

Merging 之所以好是因爲它是一個不可逆的操作。在任何情況下,現有分支不能被更改。這避免了所有 rebasing 的潛在陷阱(詳見下文)。

另一方面,這也意味着每次需要合併上游更改時, feature 分支都將有一個額外的 merge 提交產生。如果 master 非常活躍,這可能破壞你全部的 feature 分支的歷史。使用高級的 git log 選項來減緩這個問題是有可能的,也讓其他開發人員很難理解這個項目的歷史記錄。

Rebase 選項

作爲 merging 的一個替代品,你可以使用以下命令將 feature 分支合併到 master 分支:

git checkout feature
git rebase master

這將整個 feature 分支從 master 分支的頂端開始,有效地將所有新的提交合併到主分支中。但是,並不是使用合併提交,而是通過爲每個在原始分支上的提交創建全新的提交來重寫項目歷史。

img

rebasing 最主要的益處是你將獲得一個十分乾淨整潔的項目歷史。首先,它通過 git merge 排除多餘的 merge 提交需求;其次,正如你在上圖所看到的那樣,rebasing 也會產生完美線性的項目歷史記錄—你可以順着 feature 一直到項目的起始位置而沒有任何分支。可以方便的使用 git loggit bisectgitk 追蹤提交記錄。

但是,對於新的提交歷史有兩點需要權衡:安全性和可追溯性。如果你不遵循 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 ,從而產生如下所示的項目歷史記錄:

img

像這樣排除不重要的提交使你的特性歷史相當易懂。這一點是 git merge 無法比擬的。

Rebasing 的黃金規則

一旦你明白什麼是 rebasing ,最重要的事情是學習什麼時候不用它。 git rebase 的黃金法則是永遠不要在公有分支上使用它。

舉例說,想象一下如果你將 master 分支合併到 feature 分支上會發生什麼:

img

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 :

img

本地清除

最好的方法之一是合併 rebasing 到你的 工作流 以此來清理本地正在進行的 feature 分支。通過定期的執行一個交互式的 rebase ,你可以確保每一個在你的 feature 分支中的提交是集中且有意義的。這將讓你編寫你自己的代碼而不需要在獨立提交中擔心破壞它—你可以在事後修復它。

當調用 git rebase ,對於新的分支你有兩個選項:feature 父類分支(舉例說,master 分支),或者在你的 feature 分支中較早的提交。我們查看了在 交互式的 Rebasing 章節中首個選項的示例 。當你僅僅需要修復最新提交時,後者的選擇最好。舉例說,交互式 rebase 的最後3次提交顯示如下:

git checkout feature
git rebase -i HEAD~3

通過指定 HEAD~3 作爲新的基礎,事實上你並沒有移動分支—你只是交互式的重寫了接下來的3次提交。請注意,這不會將上游更改合併到 feature 分支。

img

如果你想使用這個方法重寫整個 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 mergegit rebase 合併 master 分支的上游提交。當 rebasing 通過移動你的 feature 分支到 master 分支的頭部來創建一個線性歷史時,Merging 是一個用於保護你倉庫的整個歷史記錄的安全選項。

git rebase 的作用與本地清除相似(能夠同時被執行),但是在此過程中,它合併了 master 的上游提交。

牢記,遠程分支取代 master 分支是完全合法的。這發生在其他開發者在同一個 feature 分支上協作時和你需要合併他們的更改到你的倉庫中時。

舉例說明,如果你和一個名爲 John 的開發人員添加了對 feature 分支的提交,從 John 的倉庫中獲取遠程 feature 分支後,你的倉庫看起來像如下所示:

img

你可以用與 master 分支集成上游更改相同的方法來解決這個分叉:或者你本地的 feature 分支與 john/feature 分支合併,或者 rebase 你本地 feature 分支到 john/feature 分支的頭部。

img

請注意,任何事情在未更改之前,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 分支中的機會。

img

如果你對 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技術翻譯組完成,更多國外前沿知識和乾貨好文,歡迎關注公衆號:後端面試那些事兒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章