理解git結構與簡單操做(四)合併分支的方法與策略

接上節,此時的dev分支與master分支的進度就不同了,因此須要將dev分支與master分支同步。這裏須要的就是合併分支的操做,你們應該都知道用git merge或者git rebasegit

git merge

merge,即「合併」。算法

fast-forward

當出現咱們上面圖中的那種狀況時,時間線只有一條,dev分支只不過是落後master分支而已。此時咱們在dev分支上執行git merge maseter時,git就僅僅會把dev分支指針移動到master分支所在的位置,僞裝合併了,就變成了這樣:segmentfault

這種merge的方式叫作「fast-forward」,也是git默認的merge方式。app

若是狀況改變了,舉個例子:咱們在開發過程當中,一直使用的是master分支,這時出了一個很嚴重的bug,咱們就須要創建一個叫topic的分支來處理這個bug,但主要的功能工期又不能拖,因此master分支與topic分支就同時向前推動,此時時間線如圖所示:工具

(此圖出自git本身的幫助文件,使用命令git help merge便可看到。想看其餘命令的幫助就git help <command>便可):測試

這時候,topic上的bug修改完畢,須要合併回master分支,須要的操做爲:切換到master分支git checkout master,合併devgit merge devspa

注意,這時候的這兩條分支是真正的「分支」了,他們在時間線上岔開了,各自分支都有本身獨有的東西。翻譯

由於此時的topic分支的末端並不在master分支的父端,須要把不一樣的修改同步起來,單純的指針移動不能完成這一步,fast-forward方式也就不可能實現了。設計

這時,git便會將兩個分支不一樣的地方取出,合併成一個commit,而後把master指針指向這個新的commit(就是在master上生成了一次commit)。這樣,topic分支上的修改就同步到master分支上了。此時分支狀況如圖:3d

no fast-forward

BTW,可以進行fast-forward的merge狀況下,也能夠經過增長--no-ff命令來強制不使用fast-forward模式。假如仍是回到咱們master-dev兩個分支的例子,master領先於dev分支:

這時咱們不用fast-forward,在落後的dev分支上執行git merge master --no-ff,git會在dev上強行建立一個commit,把master分支上不一樣於dev的修改加進去,分支線就會變成這種詭異的樣子:

(本手殘渣畫圖實在是很差看,就直接用GUI工具source tree上的狀況截圖了)

底層上,git會把將要合併的兩個分支的各個commit快照進行差別比較,求出它們之間的最長公共子序列,並把公共子序列從中去掉,得出各自存在兩個分支中的不一樣修改,並將其合併成一個commit放在當前分支的頂端。

這裏僅僅說明一點原理,具體實現方式與算法本人也只是懂一點皮毛,只要明白fast-forward與不使用的狀況下merge,分支會產生什麼樣的狀況,用來工做就沒有任何問題了。

squash

除了--no-ff,merge還有另外一種合併的方式:--squash。這種方法在符合fast-forward的狀況下依然會執行fast-forward方式,不會有任何改變。但當遇到以下狀況時:

假設咱們要將topic合併到master上來,squash方式會集中topic的「A、B、C」三次commit中的修改合併,並添加到暫存區中

這時master分支與topic分支不會有任何的變更,只不過暫存區中會被添加topic上修改的集合(暫存區=A+B+C)。

這時咱們就能夠查看暫存區中的內容是否是符合一次提交,以後commit就能夠了。git help merge裏是這麼描述的:「create a single commit instead of doing a merge」,結合上面的講解就能夠理解squash的意思了吧。

git merge 解決衝突

fast-forward中是沒有衝突的(不明白爲啥沒衝突的面壁思過)。而在其餘狀況時,若是兩個分支同時有對同一個文件(行)的修改,就會產生衝突。這時git會在產生衝突的文件裏寫一堆這樣的東西:

上面的「<<<<<<<< HEAD」直到「========」的部分,就是當前分支的修改(看到HEAD就知道是指向當前分支的指針了)。而「========」到下面的「>>>>>>> dev」的部分天然就是dev分支合併過來的修改啦。

這時須要你仔細對比衝突,若是跟同事合做的話就要商量好,而後把「<<<<< HEAD ===== >>>>> dev」之類git給你加上的東西和不須要的修改部分刪掉。接下來git status就會看到下面的提示:

上面綠的的東西天然就在暫存區了,這些表明dev分支上並不衝突的部分。下面的紅色文件就表明你衝突的文件,當你修改以後須要走一遍add -> commit的流程(這個commit能夠不指定commit message),也能夠直接執行git commit -a,就完成merge建立新commit的過程了。

涉及操做:git merge <branch>, git merge <branch> --no-ff, git merge <branch> --squash, git checkout <branch>, git help <command>

git rebase

除了merge,git還有一種分支合併的方式,叫作git rebase。

rebase,就是「re」與「base」結合,官方譯名「變基」(咖喱gaygayʕ •ᴥ•ʔ)。這個「變基」的含義從字面上確實不是很好理解,先來看一下rebase示例:

回到咱們master-dev兩個分支的例子,master領先於dev分支:

這時候咱們在dev分支上執行git rebase master,master便與dev合併了,如圖所示:

此時你心裏OS:這不是跟fast-forward模式下的merge同樣麼?莫急莫急,咱們再看一下出現這樣狀況下的分支(做者偷懶拿前面圖糊弄了嘿嘿嘿):

不着急解釋原理,咱們先看看在topic上執行git rebase master的結果(就是將master合併到topic上):

看圖得知:master上的「F」「G」兩次提交,變成了topic分支的父節點,整個分支又從新合成爲一條時間線。在本例中,你能夠想象「biu」的一下把topic分支拔下來,而後「pu」的一下把它插到了master的頂端(大霧)。

固然,git確定不是像上面那樣「biu」「pu」地操做分支的。

git help rebase中是這樣描述git rebase的:

git-rebase - Reapply commits on top of another base tip

翻譯一下,就是「將你的commit們在另外一個基準點上從新應用」。注意這裏的「reapply」,git並不會直接移動commit自己,而是會爲須要rebase的分支上的commit分別建立一個patch「補丁」,而後將patch在基準點上依次應用,重建出一條時間線

能夠參照上面的兩張圖梳理一下流程:

當咱們在topic分支上執行git rebase master時,表明了咱們要將咱們當前的分支(topic)應用到指定的master分支上。

此時topic與master的共同父節點是「E」,topic的特有commit是「A」「B」「C」,git就會按照時間點,分別建立「A」「B」「C」的patch「A'」「B'」「C'」,而後將topic分支的基準點設置爲master分支的頂點「G」(「變基」了!),依次將「A'」「B'」「C'」Apply到「G」上。

如今是否是理解「rebase變基」是什麼意思了!

git rebase 解決衝突

rebase產生的衝突與merge實際上是相同的。但因爲rebase操做會按照patch一個個打補丁上去,每打一個都有可能會產生衝突,跟merge的產生一個commit這種一次性操做不同,解決衝突以後也就不是提交commit,而是git add <file>以後執行git rebase --continue

也就是「打一個補丁,解決一次衝突,而後繼續下一個補丁」的過程。

若是你不耐煩了,也能夠git rebase --abort直接不進行rebase了。

涉及操做:git rebase <branch>, git rebase <branch> --onto <commit id>, git rebase --continue, git rebase --abort

關於分支處理策略的選擇

上面講了好多關於分支的東西,可能會讓人困惑:分支涉及到的東西這麼多,自己又複雜,多分支處理也複雜,應該怎樣利用分支纔好?分支合併的策略選哪種呢?這裏我說一下我的的看法:

首先,git保存的是修改這一點,能夠很清楚的讓咱們知道代碼發生了哪些改變。在這樣的狀況下,咱們利用commit時間線就能夠明確地區分哪一個人在什麼時間作了什麼事情,也就是給了你「查看歷史」與「修改歷史」的權力,這對一個軟件項目來講是相當重要的。

有關git多個分支的設計,實際上是很是巧妙的。多個分支解決了以代碼自己不一樣版本、不一樣功能或不一樣目的的開發方向(好比開發新功能或改bug,又暫時不想修改主要版本)開發時的代碼版本管理問題,可以很方便地管理工做區的文件內容。

因此,我對多分支系統利用的理解是這樣的:

  • 分支是須要充分利用的。首先要肯定一個master分支,做爲這個項目最終上線的版本,要保證合併到master分支上的代碼都是肯定無誤的、測試經過的。
  • 在開發過程當中,可使用一個development分支來開發,用其部署測試環境,使這個分支成爲能夠隨意修改的分支,增長開發的靈活度。
  • 在master或者dev分支出現問題,或者要分頭行動時,爲每一個分支在合適的父節點上建立新的功能分支或bug分支來處理這些問題,能夠保證主要的代碼不會出錯,就算是開發出問題,直接刪除該分支即是,基本不用涉及到文件層面的修改。
  • 因此,對工做區的修改應該僅限於增長新內容和修復bug之類的操做,其餘的都應該交給git去處理,保證版本樹是一條路,複雜的功能刪除也不用一行一行找。
  • 當某個分支的某些commit出現問題時,能夠先將沒有問題的部分創建分支保存起來,保證那些內容不會出問題。

有關git代碼合併策略的選擇,雖然git提供了很是豐富的方法,但一個team使用的方法應該大致固定成同一個,這樣能避免不少混亂,而後在適當的時機使用不一樣的策略。在《Pro Git》這本書中總結的就很好,我摘下來總結下:

選擇merge仍是rebase取決於你對 commit歷史時間線的定義。

有兩種觀點:第一種認爲,commit歷史應該顯示的是何時具體發生了什麼事,好比分支的建立與合併過程,有哪些分支,分別合併在了什麼地方等等。另外一種認爲,commit歷史應該顯示的是這個項目經歷過的狀態,而不考慮具體的分支構建過程。

每個團隊,每個人都是不一樣的。git做爲一個如此強大的工具提供給了你解決任何問題的思路,你就要考慮清楚你的團隊到底須要什麼。

一個一箭雙鵰的方法就是:rebase你本地的修改,push到多人環境中時用merge。

亂糟糟的時間線&&完整的分支結構 vs 清爽的一條線&&捨棄修改過程,看你團隊取捨咯。

文章連接

理解git結構與簡單操做(一)git的本質

理解git結構與簡單操做(二)工做區與暫存區

理解git結構與簡單操做(三)認識版本庫與分支

理解git結構與簡單操做(四)合併分支的方法與策略

相關文章
相關標籤/搜索