在 Git 中整合來自不一樣分支的修改主要有兩種方法:merge
以及 rebase
。 在本節中咱們將學習什麼是「rebase」,怎樣使用「rebase」,並將展現該操做的驚豔之處,以及指出在何種狀況下你應避免使用它。python
整合分支最容易的方法是 merge
命令。 它會把兩個分支的最新快照(C3
和 C4
)以及兩者最近的共同祖先(C2
)進行三方merge,merge的結果是生成一個新的快照(並提交)。git
還有一種方法:你能夠提取在 C4
中引入的補丁和修改,而後在 C3
的基礎上應用一次。 在 Git 中,這種操做就叫作 rebase。 你可使用 rebase
命令將提交到某一分支上的全部修改都移至另外一分支上,就好像「從新播放」同樣。django
在上面這個例子中,運行:服務器
$ git checkout experiment $ git rebase master First, rewinding head to replay your work on top of it... Applying: added staged command
它的原理是首先找到這兩個分支(即當前分支 experiment
、rebase操做的目標基底分支 master
)的最近共同祖先 C2
,而後對比當前分支相對於該祖先的歷次提交,提取相應的修改並存爲臨時文件,而後將當前分支指向目標基底 C3
, 最後以此將以前另存爲臨時文件的修改依序應用。併發
如今回到 master
分支,進行一次快進merge。app
$ git checkout master
$ git merge experiment
此時,C4'
指向的快照就和上面使用 merge
命令的例子中 C5
指向的快照如出一轍了。 這兩種整合方法的最終結果沒有任何區別,可是rebase使得提交歷史更加整潔。 你在查看一個通過rebase的分支的歷史記錄時會發現,儘管實際的開發工做是並行的,但它們看上去就像是串行的同樣,提交歷史是一條直線沒有分叉。ide
通常咱們這樣作的目的是爲了確保在向遠程分支推送時能保持提交歷史的整潔——例如向某個其餘人維護的項目貢獻代碼時。 在這種狀況下,你首先在本身的分支裏進行開發,當開發完成時你須要先將你的代碼rebase到 origin/master
上,而後再向主項目提交修改。 這樣的話,該項目的維護者就再也不須要進行整合工做,只須要快進merge即可。工具
請注意,不管是經過rebase,仍是經過三方merge,整合的最終結果所指向的快照始終是同樣的,只不過提交歷史不一樣罷了。 rebase是將一系列提交按照原有次序依次應用到另外一分支上,而merge是把最終結果合在一塊兒。學習
在對兩個分支進行rebase時,所生成的「重放」並不必定要在目標分支上應用,你也能夠指定另外的一個分支進行應用。 你建立了一個特性分支 server
,爲服務端添加了一些功能,提交了 C3
和 C4
。 而後從 C3
上建立了特性分支 client
,爲客戶端添加了一些功能,提交了 C8
和 C9
。 最後,你回到 server
分支,又提交了 C10
。測試
假設你但願將 client
中的修改合併到主分支併發布,但暫時並不想merge server
中的修改,由於它們還須要通過更全面的測試。 這時,你就可使用 git rebase
命令的 --onto
選項,選中在 client
分支裏但不在 server
分支裏的修改(即 C8
和 C9
),將它們在 master
分支上重放:
$ git rebase --onto master server client
以上命令的意思是:「取出 client
分支,找出處於 client
分支和 server
分支的共同祖先以後的修改,而後把它們在 master
分支上重放一遍」。 這理解起來有一點複雜,不過效果很是酷。
如今能夠快進merge master
分支了。
$ git checkout master
$ git merge client
接下來你決定將 server
分支中的修改也整合進來。 使用 git rebase [basebranch] [topicbranch]
命令能夠直接將特性分支(即本例中的 server
)rebase到目標分支(即 master
)上。這樣作能省去你先切換到 server
分支,再對其執行rebase命令的多個步驟。
$ git rebase master server
而後就能夠快進merge主分支 master 了:
$ git checkout master
$ git merge server
至此,client
和 server
分支中的修改都已經整合到主分支裏了,你能夠刪除這兩個分支,最終提交歷史會變成圖 最終的提交歷史 中的樣子:
$ git branch -d client
$ git branch -d server
呃,奇妙的rebase也並不是天衣無縫,要用它得遵照一條準則:
Do not rebase commits that exist outside your repository.
若是你遵循這條金科玉律,就不會出差錯。
rebase操做的實質是丟棄一些現有的提交,而後相應地新建一些內容同樣但實際上不一樣的提交。 若是你已經將提交推送至某個倉庫,而其餘人也已經從該倉庫拉取提交併進行了後續工做,此時,若是你用 git rebase
命令從新整理了提交併再次推送,你的同伴所以將不得再也不次將他們手頭的工做與你的提交進行整合,若是接下來你還要拉取並整合他們修改過的提交,事情就會變得一團糟。
讓咱們來看一個在公開的倉庫上執行rebase操做所帶來的問題。 假設你從一箇中央服務器克隆而後在它的基礎上進行了一些開發。 你的提交歷史如圖所示:
而後,某人又向中央服務器提交了一些修改,其中還包括一次merge。 你抓取了這些在遠程分支上的修改,並將其merge到你本地的開發分支,而後你的提交歷史就會變成這樣:
接下來,這我的又決定把merge操做回滾,改用rebase;繼而又用 git push --force
命令覆蓋了服務器上的提交歷史。 以後你從服務器抓取更新,會發現多出來一些新的提交。
結果就是大家兩人的處境都十分尷尬。 若是你執行 git pull
命令,你將merge來自兩條提交歷史的內容,生成一個新的merge提交,最終倉庫會如圖所示:
此時若是你執行 git log
命令,你會發現有兩個提交的做者、日期、日誌竟然是同樣的,這會使人感到混亂。 此外,若是你將這一堆又推送到服務器上,你其實是將那些已經被rebase拋棄的提交又找了回來,這會使人感到更加混亂。 很明顯對方並不想在提交歷史中看到 C4
和 C6
,由於以前就是他把這兩個提交經過rebase丟棄的。
若是你 真的 遭遇了相似的處境,Git 還有一些高級魔法能夠幫到你。 參考 https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA
至此,你已在實戰中學習了rebase和merge的用法,你必定會想問,到底哪一種方式更好。 在回答這個問題以前,讓咱們退後一步,想討論一下提交歷史到底意味着什麼。
有一種觀點認爲,倉庫的提交歷史便是 記錄實際發生過什麼。 它是針對歷史的文檔,自己就有價值,不能亂改。 從這個角度看來,改變提交歷史是一種褻瀆,你使用_謊話_掩蓋了實際發生過的事情。 若是由merge產生的提交歷史是一團糟怎麼辦? 既然事實就是如此,那麼這些痕跡就應該被保留下來,讓後人可以查閱。
另外一種觀點則正好相反,他們認爲提交歷史是 項目過程當中發生的事。 沒人會出版一本書的初版草稿,軟件維護手冊也是須要反覆修訂才能方便使用。 持這一觀點的人會使用 rebase 及 filter-branch 等工具來編寫故事,怎麼方便後來的讀者就怎麼寫。
如今,讓咱們回到以前的問題上來,到底merge仍是rebase好?但願你能明白,這並無一個簡單的答案。 Git 是一個很是強大的工具,它容許你對提交歷史作許多事情,但每一個團隊、每一個項目對此的需求並不相同。 既然你已經分別學習了二者的用法,相信你可以根據實際狀況做出明智的選擇。
總的原則是,只對還沒有推送或分享給別人的本地修改執行rebase操做清理歷史,從不對已推送至別處的提交執行rebase操做,這樣,你才能享受到兩種方式帶來的便利。