Merging 和 Rebasing 的大比拼

雖然 merging 和 rebasing 在 git 中類似時,但他們提供不一樣的功能。爲了讓你的歷史儘量的乾淨和完整,你應該知道如下幾點。git

git rebase 命令已 神奇的 Git voodoo 而聞名,初學者應該遠離它,但它實際上可讓開發團隊在使用時更加輕鬆。在本章中,咱們將 把 git rebase 和與之有關聯的 git merge 命令相比較 ,並在典型的 Git 工做流 中從新定位,識別其全部潛在的機會。面試

概述

首先要明白關於 git rebase 的事情是它像 git merge 同樣解決相同的問題。git rebase 和 git merge 同樣都是被設計用於從一個分支獲取併合併到當前分支,可是他們採起不一樣的工做方式。spring

考慮一下,當你開始在 一個專用的分支上開發新特性,與此同時另外一個團隊成員用新的提交來更新了 master 分支時,會發生什麼呢?這會致使分叉的歷史記錄,對於這個問題,使用 Git 做爲協同工具的任何人來講都應該很熟悉。後端

如今,假設你在工做時在 master 上的新提交與新特性相關。爲了將新提交合併到你的 feature 分支上,你有兩種選擇:merging 或者 rebasing。安全

Merge 選項

最簡單的選項是使用如下命令將 master 分支合併到 feature 分支:編輯器

git checkout feature
git merge master

或者,你能夠簡化成一句:svg

git merge master feature

這將在 feature 分支上建立一個新 「 合併提交 」 ,並把兩個分支的歷史聯繫在一塊兒。分支結構顯示以下:工具

img

Merging 之因此好是由於它是一個不可逆的操做。在任何狀況下,現有分支不能被更改。這避免了全部 rebasing 的潛在陷阱(詳見下文)。學習

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

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/me...
譯文首發: http://didispace.com/git-merg...
本文有spring4all技術翻譯組完成,更多國外前沿知識和乾貨好文,歡迎關注公衆號:後端面試那些事兒。
相關文章
相關標籤/搜索