接上節,此時的dev分支與master分支的進度就不同了,因此須要將dev分支與master分支同步。這裏須要的就是合併分支的操做,你們應該都知道用git merge
或者git rebase
。git
merge,即「合併」。算法
當出現咱們上面圖中的那種狀況時,時間線只有一條,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 dev
。spa
注意,這時候的這兩條分支是真正的「分支」了,他們在時間線上岔開了,各自分支都有本身獨有的東西。翻譯
由於此時的topic分支的末端並不在master分支的父端,須要把不一樣的修改同步起來,單純的指針移動不能完成這一步,fast-forward方式也就不可能實現了。設計
這時,git便會將兩個分支不一樣的地方取出,合併成一個commit,而後把master指針指向這個新的commit(就是在master上生成了一次commit)。這樣,topic分支上的修改就同步到master分支上了。此時分支狀況如圖:3d
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,分支會產生什麼樣的狀況,用來工做就沒有任何問題了。
除了--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的意思了吧。
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>
除了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變基」是什麼意思了!
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,又暫時不想修改主要版本)開發時的代碼版本管理問題,可以很方便地管理工做區的文件內容。
因此,我對多分支系統利用的理解是這樣的:
有關git代碼合併策略的選擇,雖然git提供了很是豐富的方法,但一個team使用的方法應該大致固定成同一個,這樣能避免不少混亂,而後在適當的時機使用不一樣的策略。在《Pro Git》這本書中總結的就很好,我摘下來總結下:
選擇merge仍是rebase取決於你對 commit歷史時間線的定義。有兩種觀點:第一種認爲,commit歷史應該顯示的是何時具體發生了什麼事,好比分支的建立與合併過程,有哪些分支,分別合併在了什麼地方等等。另外一種認爲,commit歷史應該顯示的是這個項目經歷過的狀態,而不考慮具體的分支構建過程。
每個團隊,每個人都是不一樣的。git做爲一個如此強大的工具提供給了你解決任何問題的思路,你就要考慮清楚你的團隊到底須要什麼。
一個一箭雙鵰的方法就是:rebase你本地的修改,push到多人環境中時用merge。
亂糟糟的時間線&&完整的分支結構 vs 清爽的一條線&&捨棄修改過程,看你團隊取捨咯。