理解git结构与简单操作(四)合并分支的方法与策略
接上節(jié),此時(shí)的dev分支與master分支的進(jìn)度就不一樣了,所以需要將dev分支與master分支同步。這里需要的就是合并分支的操作,大家應(yīng)該都知道用git merge或者git rebase。
git merge
merge,即「合并」。
fast-forward
當(dāng)出現(xiàn)我們上面圖中的那種情況時(shí),時(shí)間線只有一條,dev分支只不過(guò)是落后master分支而已。此時(shí)我們?cè)赿ev分支上執(zhí)行g(shù)it merge maseter時(shí),git就僅僅會(huì)把dev分支指針移動(dòng)到master分支所在的位置,假裝合并了,就變成了這樣:
這種merge的方式叫做「fast-forward」,也是git默認(rèn)的merge方式。
如果情況改變了,舉個(gè)例子:我們?cè)陂_發(fā)過(guò)程中,一直使用的是master分支,這時(shí)出了一個(gè)很嚴(yán)重的bug,我們就需要建立一個(gè)叫topic的分支來(lái)處理這個(gè)bug,但主要的功能工期又不能拖,所以master分支與topic分支就同時(shí)向前推進(jìn),此時(shí)時(shí)間線如圖所示:
(此圖出自git自己的幫助文件,使用命令git help merge即可看到。想看其他命令的幫助就git help <command>即可):
這時(shí)候,topic上的bug修改完畢,需要合并回master分支,需要的操作為:切換到master分支git checkout master,合并devgit merge dev。
注意,這時(shí)候的這兩條分支是真正的「分支」了,他們?cè)跁r(shí)間線上岔開了,各自分支都有自己獨(dú)有的東西。
因?yàn)榇藭r(shí)的topic分支的末端并不在master分支的父端,需要把不同的修改同步起來(lái),單純的指針移動(dòng)不能完成這一步,fast-forward方式也就不可能實(shí)現(xiàn)了。
這時(shí),git便會(huì)將兩個(gè)分支不同的地方取出,合并成一個(gè)commit,然后把master指針指向這個(gè)新的commit(就是在master上生成了一次commit)。這樣,topic分支上的修改就同步到master分支上了。此時(shí)分支情況如圖:
no fast-forward
BTW,能夠進(jìn)行fast-forward的merge情況下,也可以通過(guò)增加--no-ff命令來(lái)強(qiáng)制不使用fast-forward模式。假如還是回到我們master-dev兩個(gè)分支的例子,master領(lǐng)先于dev分支:
這時(shí)我們不用fast-forward,在落后的dev分支上執(zhí)行g(shù)it merge master --no-ff,git會(huì)在dev上強(qiáng)行創(chuàng)建一個(gè)commit,把master分支上不同于dev的修改加進(jìn)去,分支線就會(huì)變成這種詭異的樣子:
(本手殘?jiān)媹D實(shí)在是不好看,就直接用GUI工具source tree上的情況截圖了)
底層上,git會(huì)把將要合并的兩個(gè)分支的各個(gè)commit快照進(jìn)行差異比較,求出它們之間的最長(zhǎng)公共子序列,并把公共子序列從中去掉,得出各自存在兩個(gè)分支中的不同修改,并將其合并成一個(gè)commit放在當(dāng)前分支的頂端。
這里僅僅說(shuō)明一點(diǎn)原理,具體實(shí)現(xiàn)方式與算法本人也只是懂一點(diǎn)皮毛,只要明白fast-forward與不使用的情況下merge,分支會(huì)產(chǎn)生什么樣的情況,用來(lái)工作就沒(méi)有任何問(wèn)題了。
squash
除了--no-ff,merge還有另一種合并的方式:--squash。這種方法在符合fast-forward的情況下依然會(huì)執(zhí)行fast-forward方式,不會(huì)有任何改變。但當(dāng)遇到如下情況時(shí):
假設(shè)我們要將topic合并到master上來(lái),squash方式會(huì)集中topic的「A、B、C」三次commit中的修改合并,并添加到暫存區(qū)中。
這時(shí)master分支與topic分支不會(huì)有任何的變動(dòng),只不過(guò)暫存區(qū)中會(huì)被添加topic上修改的集合(暫存區(qū)=A+B+C)。
這時(shí)我們就可以查看暫存區(qū)中的內(nèi)容是不是符合一次提交,之后commit就可以了。git help merge里是這么描述的:「create a single commit instead of doing a merge」,結(jié)合上面的講解就可以理解squash的意思了吧。
git merge 解決沖突
fast-forward中是沒(méi)有沖突的(不明白為啥沒(méi)沖突的面壁思過(guò))。而在其他情況時(shí),如果兩個(gè)分支同時(shí)有對(duì)同一個(gè)文件(行)的修改,就會(huì)產(chǎn)生沖突。這時(shí)git會(huì)在產(chǎn)生沖突的文件里寫一堆這樣的東西:
上面的「<<<<<<<< HEAD」直到「========」的部分,就是當(dāng)前分支的修改(看到HEAD就知道是指向當(dāng)前分支的指針了)。而「========」到下面的「>>>>>>> dev」的部分自然就是dev分支合并過(guò)來(lái)的修改啦。
這時(shí)需要你仔細(xì)對(duì)比沖突,如果跟同事合作的話就要商量好,然后把「<<<<< HEAD ===== >>>>> dev」之類git給你加上的東西和不需要的修改部分刪掉。接下來(lái)git status就會(huì)看到下面的提示:
上面綠的的東西自然就在暫存區(qū)了,這些代表dev分支上并不沖突的部分。下面的紅色文件就代表你沖突的文件,當(dāng)你修改之后需要走一遍add -> commit的流程(這個(gè)commit可以不指定commit message),也可以直接執(zhí)行g(shù)it commit -a,就完成merge創(chuàng)建新commit的過(guò)程了。
涉及操作: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」結(jié)合,官方譯名「變基」(咖喱gaygay? ????)。這個(gè)「變基」的含義從字面上確實(shí)不是很好理解,先來(lái)看一下rebase示例:
回到我們master-dev兩個(gè)分支的例子,master領(lǐng)先于dev分支:
這時(shí)候我們?cè)赿ev分支上執(zhí)行g(shù)it rebase master,master便與dev合并了,如圖所示:
此時(shí)你內(nèi)心OS:這不是跟fast-forward模式下的merge一樣么?莫急莫急,我們?cè)倏匆幌鲁霈F(xiàn)這樣情況下的分支(作者偷懶拿前面圖糊弄了嘿嘿嘿):
不著急解釋原理,我們先看看在topic上執(zhí)行g(shù)it rebase master的結(jié)果(就是將master合并到topic上):
看圖得知:master上的「F」「G」兩次提交,變成了topic分支的父節(jié)點(diǎn),整個(gè)分支又重新合成為一條時(shí)間線。在本例中,你可以想象「biu」的一下把topic分支拔下來(lái),然后「pu」的一下把它插到了master的頂端(大霧)。
當(dāng)然,git肯定不是像上面那樣「biu」「pu」地操作分支的。
git help rebase中是這樣描述git rebase的:
git-rebase - Reapply commits on top of another base tip翻譯一下,就是「將你的commit們?cè)诹硪粋€(gè)基準(zhǔn)點(diǎn)上重新應(yīng)用」。注意這里的「reapply」,git并不會(huì)直接移動(dòng)commit本身,而是會(huì)為需要rebase的分支上的commit分別創(chuàng)建一個(gè)patch「補(bǔ)丁」,然后將patch在基準(zhǔn)點(diǎn)上依次應(yīng)用,重建出一條時(shí)間線。
可以參照上面的兩張圖梳理一下流程:
當(dāng)我們?cè)趖opic分支上執(zhí)行g(shù)it rebase master時(shí),代表了我們要將我們當(dāng)前的分支(topic)應(yīng)用到指定的master分支上。
此時(shí)topic與master的共同父節(jié)點(diǎn)是「E」,topic的特有commit是「A」「B」「C」,git就會(huì)按照時(shí)間點(diǎn),分別創(chuàng)建「A」「B」「C」的patch「A'」「B'」「C'」,然后將topic分支的基準(zhǔn)點(diǎn)設(shè)置為master分支的頂點(diǎn)「G」(「變基」了!),依次將「A'」「B'」「C'」Apply到「G」上。
現(xiàn)在是不是理解「rebase變基」是什么意思了!
git rebase 解決沖突
rebase產(chǎn)生的沖突與merge其實(shí)是相同的。但由于rebase操作會(huì)按照patch一個(gè)個(gè)打補(bǔ)丁上去,每打一個(gè)都有可能會(huì)產(chǎn)生沖突,跟merge的產(chǎn)生一個(gè)commit這種一次性操作不一樣,解決沖突之后也就不是提交commit,而是git add <file>之后執(zhí)行g(shù)it rebase --continue。
也就是「打一個(gè)補(bǔ)丁,解決一次沖突,然后繼續(xù)下一個(gè)補(bǔ)丁」的過(guò)程。
如果你不耐煩了,也可以git rebase --abort直接不進(jìn)行rebase了。
涉及操作:git rebase <branch>, git rebase <branch> --onto <commit id>, git rebase --continue, git rebase --abort
關(guān)于分支處理策略的選擇
上面講了好多關(guān)于分支的東西,可能會(huì)讓人困惑:分支涉及到的東西這么多,本身又復(fù)雜,多分支處理也復(fù)雜,應(yīng)該怎樣利用分支才好?分支合并的策略選哪一種呢?這里我說(shuō)一下個(gè)人的見解:
首先,git保存的是修改這一點(diǎn),可以很清楚的讓我們知道代碼發(fā)生了哪些改變。在這樣的情況下,我們利用commit時(shí)間線就可以明確地區(qū)分哪個(gè)人在什么時(shí)間做了什么事情,也就是給了你「查看歷史」與「修改歷史」的權(quán)力,這對(duì)一個(gè)軟件項(xiàng)目來(lái)說(shuō)是至關(guān)重要的。
有關(guān)git多個(gè)分支的設(shè)計(jì),其實(shí)是非常巧妙的。多個(gè)分支解決了以代碼本身不同版本、不同功能或不同目的的開發(fā)方向(比如開發(fā)新功能或改bug,又暫時(shí)不想修改主要版本)開發(fā)時(shí)的代碼版本管理問(wèn)題,能夠很方便地管理工作區(qū)的文件內(nèi)容。
所以,我對(duì)多分支系統(tǒng)利用的理解是這樣的:
- 分支是需要充分利用的。首先要確定一個(gè)master分支,作為這個(gè)項(xiàng)目最終上線的版本,要保證合并到master分支上的代碼都是確定無(wú)誤的、測(cè)試通過(guò)的。
- 在開發(fā)過(guò)程中,可以使用一個(gè)development分支來(lái)開發(fā),用其部署測(cè)試環(huán)境,使這個(gè)分支成為可以隨意修改的分支,增加開發(fā)的靈活度。
- 在master或者dev分支出現(xiàn)問(wèn)題,或者要分頭行動(dòng)時(shí),為每個(gè)分支在合適的父節(jié)點(diǎn)上創(chuàng)建新的功能分支或bug分支來(lái)處理這些問(wèn)題,可以保證主要的代碼不會(huì)出錯(cuò),就算是開發(fā)出問(wèn)題,直接刪除該分支便是,基本不用涉及到文件層面的修改。
- 所以,對(duì)工作區(qū)的修改應(yīng)該僅限于增加新內(nèi)容和修復(fù)bug之類的操作,其他的都應(yīng)該交給git去處理,保證版本樹是一條路,復(fù)雜的功能刪除也不用一行一行找。
- 當(dāng)某個(gè)分支的某些commit出現(xiàn)問(wèn)題時(shí),可以先將沒(méi)有問(wèn)題的部分建立分支保存起來(lái),保證那些內(nèi)容不會(huì)出問(wèn)題。
有關(guān)git代碼合并策略的選擇,雖然git提供了非常豐富的方法,但一個(gè)team使用的方法應(yīng)該大體固定成同一個(gè),這樣能避免很多混亂,然后在適當(dāng)?shù)臅r(shí)機(jī)使用不同的策略。在《Pro Git》這本書中總結(jié)的就很好,我摘下來(lái)總結(jié)下:
選擇merge還是rebase取決于你對(duì)commit歷史時(shí)間線的定義。有兩種觀點(diǎn):第一種認(rèn)為,commit歷史應(yīng)該顯示的是什么時(shí)候具體發(fā)生了什么事,比如分支的創(chuàng)建與合并過(guò)程,有哪些分支,分別合并在了什么地方等等。另一種認(rèn)為,commit歷史應(yīng)該顯示的是這個(gè)項(xiàng)目經(jīng)歷過(guò)的狀態(tài),而不考慮具體的分支構(gòu)建過(guò)程。
每一個(gè)團(tuán)隊(duì),每一個(gè)人都是不同的。git作為一個(gè)如此強(qiáng)大的工具提供給了你解決任何問(wèn)題的思路,你就要考慮清楚你的團(tuán)隊(duì)到底需要什么。
一個(gè)兩全其美的方法就是:rebase你本地的修改,push到多人環(huán)境中時(shí)用merge。
亂糟糟的時(shí)間線&&完整的分支結(jié)構(gòu) vs 清爽的一條線&&舍棄修改過(guò)程,看你團(tuán)隊(duì)取舍咯。
文章鏈接
理解git結(jié)構(gòu)與簡(jiǎn)單操作(一)git的本質(zhì)
理解git結(jié)構(gòu)與簡(jiǎn)單操作(二)工作區(qū)與暫存區(qū)
理解git結(jié)構(gòu)與簡(jiǎn)單操作(三)認(rèn)識(shí)版本庫(kù)與分支
理解git結(jié)構(gòu)與簡(jiǎn)單操作(四)合并分支的方法與策略
總結(jié)
以上是生活随笔為你收集整理的理解git结构与简单操作(四)合并分支的方法与策略的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 计算机系毕业论文绪论,本科毕业论文绪论范
- 下一篇: 南师大632c语言程序设计,单片机c语言