Git笔记(三)——[cherry-pick, merge, rebase]
書(shū)接上回,直入主題!這篇繼續(xù)實(shí)踐剩下的幾個(gè)命令。
現(xiàn)在的SourceTree狀態(tài)如下:
cherry-pick - 媽媽,我也要
cherry-pick其實(shí)在工作中還挺常用的,一種常見(jiàn)的場(chǎng)景就是,比如我在A分支做了幾次commit以后,發(fā)現(xiàn)其實(shí)我并不應(yīng)該在A分支上工作,應(yīng)該在B分支上工作,這時(shí)就需要將這些commit從A分支復(fù)制到B分支去了,這時(shí)候就需要cherry-pick命令了,B分支指著這些commit說(shuō):媽媽,我也要!比如說(shuō),我們?cè)趍aster分支上繼續(xù)做兩次提交,第一次添加一行”test 10”,git commit -am "commit 10",第二次添加“test 11”,到達(dá)如下圖的狀態(tài):
這個(gè)時(shí)候我們發(fā)現(xiàn),哦NO,我們不應(yīng)該直接更改master分支,我們應(yīng)該在自己的分支上做提交。這個(gè)時(shí)候先新建一個(gè)分支git checkout -b branch3 1a222c3,注意這里的最后一個(gè)參數(shù)是新分支的起點(diǎn),也就是說(shuō),新的分支branch3是從“commit 8,9”開(kāi)始的,現(xiàn)在我們需要把剛才的兩次提交移動(dòng)到新的分支上。運(yùn)行g(shù)it cherry-pick 0bda20e 1a04d5f,命令行會(huì)給出提示兩個(gè)commit被復(fù)制到了當(dāng)前分支上,此時(shí)SourceTree的狀態(tài)如下圖:
確定這兩個(gè)commit被復(fù)制到指定分支以后,在master分支上將這兩個(gè)commit刪除。先切回master分支:git checkout master,運(yùn)行g(shù)it reset --hard 1a222c3,此時(shí)SourceTree的狀態(tài)圖為:
兩個(gè)commit被成功的從master分支移動(dòng)到了branch3分支。
merge - 求合體
merge命令應(yīng)該也是非常的常用,比如新開(kāi)了一個(gè)分支去完成某個(gè)feature,然后完成了以后要merge到master分支來(lái)。就拿剛才的例子來(lái)講,開(kāi)了新分支branch3來(lái)存放“commit 10”和“commit 11”,這兩個(gè)commit可以看成是新的feature,完事以后就要合并到master分支上了。但合并之前,一般要將master分支當(dāng)前最新的commit合并到branch3上,因?yàn)槟愕腷ranch3的起點(diǎn)此時(shí)可能已經(jīng)不是master分支的最新commit了。切換到branch3分支上運(yùn)行g(shù)it merge master,Git提示“Already up-to-date.”,這說(shuō)明當(dāng)前所在的分支branch3比master分支還要新,branch3上的commit都是master最新commit點(diǎn)的子commit,故不需要合并。切回master分支git checkout master,將分支branch3合并到master分支上,git merge branch3,結(jié)果如下圖:
可以看到branch3分支上的更改已經(jīng)被合并到master上,其中“Fast-forward”是合并的一種類(lèi)型,當(dāng)當(dāng)前分支(master)是目標(biāo)分支(branch3)的祖先commit時(shí)會(huì)發(fā)生這種“Fast-forward”合并,其實(shí)可以理解為HEAD指針指向的快速移動(dòng)。
前面看到的兩種情況都是比較簡(jiǎn)單的合并,沒(méi)有遇到任何“沖突”,我們來(lái)一次稍微復(fù)雜點(diǎn)的合并,比如我們需要將branch2合并到master上。在master分支上git merge branch2,哦NO,命令行提示“Automatic merge failed”,出現(xiàn)沖突了,Git無(wú)法判斷如何merge,這個(gè)時(shí)候我們就需要手動(dòng)解決沖突以后再提交。打開(kāi)test.txt文件可以看到如下圖的內(nèi)容:
從圖28中可以看到<<<<<<<< HEAD和========之間的內(nèi)容是當(dāng)前分支的內(nèi)容,而=======和>>>>>>>>> branch2之間的內(nèi)容是branch2分支的內(nèi)容,由于改動(dòng)了同一行所以Git無(wú)法自動(dòng)合并了。這個(gè)時(shí)候你可以決定最后保留哪些內(nèi)容,我們就簡(jiǎn)單的將“test aaa”插入到第4行,然后刪除多余的標(biāo)記保存,另外,需要手動(dòng)做一次提交來(lái)解決沖突:git add test.txt,然后這次我們不用-m參數(shù)直接git commit,可以看到如下圖的提示:
Git已經(jīng)發(fā)現(xiàn)我們是在做一次merge的提交,并且是“解決沖突的merge”,可以看到Git默認(rèn)給出的提交信息非常的友好,包含了合并的分支上的commit,也寫(xiě)明了沖突的文件是什么,我們就使用Git默認(rèn)的提交信息,直接:wq保存退出。此時(shí)可以直觀的從SourceTree里看到branch2分支的線已經(jīng)合并到master上了,如下圖所示:
總結(jié)一下merge的集中情況:
- 如果目標(biāo)分支是當(dāng)前分支的祖先commit節(jié)點(diǎn),則merge什么也不會(huì)發(fā)生,因?yàn)楫?dāng)前分支已經(jīng)是最新的了
- 如果當(dāng)前分支是目標(biāo)分支的祖先commit節(jié)點(diǎn),這時(shí)會(huì)發(fā)生Fast-forward的merge,merge的結(jié)果是簡(jiǎn)單的移動(dòng)HEAD指針
rebase - 我是直男,不喜歡彎的
從圖31可以看出,branch2分支(紫色的線)最終交匯到master分支上(藍(lán)色的線),這還只是合并了一次,并且我們當(dāng)前的分支才幾個(gè),如果分支很多并且頻繁合并的話,這樣彎彎曲曲的線會(huì)非常多,搞得你眼花繚亂,根本搞不清楚走向,顯然,直男一向是不喜歡彎的,那能把它掰直嗎?答案是肯定的,rebase就是干這個(gè)事情的!為了看清楚merge和rebase的區(qū)別,我們先將剛才branch2的合并取消,使master回滾到“commit 11”的狀態(tài):git reset --hard dda0f7d。這時(shí)我們位于master分支上,運(yùn)行g(shù)it rebase branch2,Git同樣提示我們有沖突,只是這次提示非常長(zhǎng),如下圖:
根據(jù)提示我們就可以發(fā)現(xiàn)rebase的流程,從“Applying: commit 3”這句就可以看出來(lái),其實(shí)rebase的原理是先找到兩個(gè)分支的共同祖先commit節(jié)點(diǎn)“commit 2,2.5”,然后把master分支上這個(gè)節(jié)點(diǎn)的兒子節(jié)點(diǎn)全部“應(yīng)用”到branch2分支上。從圖31中可以看到第一個(gè)兒子節(jié)點(diǎn)是“commit 3”,所以先apply這個(gè),而“commit 3”和“commit aaa”編輯的都是第4行,所以立即出現(xiàn)了沖突!照例我們需要手動(dòng)解決沖突。這次如果我們還是把“test aaa”放第4行,然后“test 3”放在第5行,那很明顯后面的“commit 4”在apply時(shí)仍然會(huì)有沖突,所以為了方便,我們直接把“test 3”和“test aaa”都放在第4行,如下圖:
然后保存,注意這時(shí)需要先把改動(dòng)add以后再操作:git add test.txt。按照?qǐng)D32的提示運(yùn)行g(shù)it rebase --continue,結(jié)果怎么樣呢?哦NO,又尼瑪沖突了!!!為嘛啊!“commit 4”的第4行還是“test 3”,而我們現(xiàn)在的第4行是“test 3 test aaa”,所以還是沖突!看來(lái)rebase在apply每一個(gè)commit時(shí)并不只是apply這個(gè)commit上的變化(即“test 4”這一行),而是apply整個(gè)文件!可以想象這樣走下去每一步都會(huì)有沖突,唯一的辦法就是放棄“test aaa”這個(gè)更改。真的是這樣嗎?其實(shí)不然,先運(yùn)行g(shù)it rebase --abort放棄這次rebase。其實(shí)這種場(chǎng)景下并不適合rebase,一般我們把別的分支合并到master時(shí)用merge,而把master合并到別的分支時(shí)會(huì)用到rebase,那我們換個(gè)思路,切換到branch2分支上git checkout branch2,然后運(yùn)行rebase命令git rebase master,顯然,還是會(huì)有沖突的,這次我們直接把“commit aaa”加到最后一行保存。運(yùn)行g(shù)it add test.txt,然后繼續(xù)進(jìn)行g(shù)it rebase --continue,可以看到結(jié)果成功了“Applying: commit aaa”。這時(shí)的SourceTree如下圖:
可是這樣與merge的結(jié)果恰恰相反,我們是把master分支merge到branch2上了,而我們初衷是將branch2分支merge到master上。簡(jiǎn)單!先切回master分支git checkout master然后運(yùn)行g(shù)it merge branch2即可,因?yàn)檫@時(shí)master分支是branch2分支的祖先commit節(jié)點(diǎn),所以直接Fast-forward了!最終的狀態(tài)圖如下圖所示:
從圖35中可以看出,達(dá)到了和圖31同樣的效果,不同的是:首先,沒(méi)有了“彎彎”的線;其次,多余的merge那一條commit沒(méi)有啦!這才是直男喜歡的!
總結(jié)一下rebase:
- 把master分支合并到別的分支用rebase,把別的分支合并到master分支上用merge
- rebase不會(huì)產(chǎn)生多余的commit,并且保持直線
關(guān)于rebase其他要補(bǔ)充的:
當(dāng)然rebase還有其他很多很牛逼的功能,其“交互模式”可以讓你干很多事情,比如調(diào)整commit的順序啊,合并一些commit啊,刪除一些commit啊等等,通過(guò)-i參數(shù)可以實(shí)現(xiàn),當(dāng)然這個(gè)命令有些復(fù)雜,我們可以使用SourceTree的圖形化界面更直觀的使用它。比如“commit 10”和“commit 11”太不和諧了,早就看你們不爽了,人家前面的“6,7”和“8,9”都成雙成對(duì),就你倆不和諧,我要“整頓”一下!在SourceTree中的“commit 8,9”上右擊,點(diǎn)擊子菜單中的“Rebase children of 1a222c3 interactively…”:
然后出現(xiàn)了圖37中的對(duì)話框,我們想整理的2個(gè)commit顯示在其中,“commit aaa”也是兒子節(jié)點(diǎn),所以也顯示在這里,我們可以通過(guò)紅色方框中的按鈕來(lái)進(jìn)行操作。比如,我們想合并一下“commit 10”和“commit 11”,兩個(gè)commit合并為“commit 10,11”。在這個(gè)對(duì)話框中可以非常直觀的進(jìn)行上面的操作,先選中“commit 11”這一行,選擇紅框中的“Squash with previous”按鈕,可以看到出現(xiàn)了一條新的commit——“[2 commits]”,如圖38所示:
選中這條commit,點(diǎn)擊紅框中的“Edit message”,出現(xiàn)了更改commit信息的對(duì)話框,如下圖:
輸入“commit 10,11”即可,點(diǎn)擊OK保存,再次點(diǎn)擊OK完成此次rebase。此時(shí)SourceTree的狀態(tài)如圖40所示:
可以看到,目的達(dá)到了,commit hash也跟原來(lái)的任何兩個(gè)都不一樣了。除了合并commit,利用圖37的紅色方框里的按鈕,還可以實(shí)現(xiàn)刪除commit,調(diào)整commit的順序等功能,大家自己嘗試吧,小心產(chǎn)生“沖突”哦!
好啦,到這里,感覺(jué)比較重要的git命令都介紹完了!!!其實(shí)這三篇都是小實(shí)踐,要想融會(huì)貫通,還需要在真實(shí)項(xiàng)目中的大實(shí)踐!
from:?http://pinkyjie.com/2014/08/10/git-notes-part-3/
總結(jié)
以上是生活随笔為你收集整理的Git笔记(三)——[cherry-pick, merge, rebase]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: git cherry-pick 使用指南
- 下一篇: Git笔记(二)——[diff, res