Git應用詳解第九講:Git cherry-pick與Git rebase

第九講:Git cherry-pick與Git rebase

前言
這一節主要介紹git cherry-pick與git rebase的原理及使用。git

1、
Git cherry-pick

Git cherry-pick的做用爲移植提交。好比在dev分支錯誤地進行了兩次提交2nd和3rd,若是想要將這兩次提交移植到master分支上。採用先刪除再添加的方法將會很繁瑣,而使用cherry-pick就能輕鬆實現這一需求。github

首先在版本庫中建立了兩個分支master和dev,並模擬上述場景:
Git應用詳解第九講:Git cherry-pick與Git rebasevim

image-20200418213440673
能夠看到,在dev分支上進行了兩次提交,在master分支上只進行了一次提交。如今想要將這兩次提交「移植」到master分支上。總體分爲兩步:app

「第一步」:將dev分支上多餘的兩次提交移植到master分支上;
「第二步」:刪除dev分支上多餘的兩次提交;
1.第一步
git cherry-pick commit_id
首先切換到master分支,而後使用以下命令將dev分支上的兩次提交移植到master分支上:編輯器

//移植2nd提交
git cherry-pick 009dd
//移植3rd提交
git cherry-pick aec8c

009dd和aec8c分別表示須要移植的提交2nd和3rd的SHA1值:
Git應用詳解第九講:Git cherry-pick與Git rebaseide

image-20200418215229274
移植過程爲:
Git應用詳解第九講:Git cherry-pick與Git rebase翻譯

image-20200418220353735
如上圖所示,執行了兩次cherry-pick指令,建立了兩個內容與2nd、3rd一致的提交對象50477和f05a0。因此,cherry-pick指令移植提交的實質是:先將須要移植的提交複製一份,再拼接到master分支上,簡稱「先複製,再拼接」;3d

上面按照順序先移植了提交2nd再移植提交3rd,不會發生衝突;指針

不按順序移植,如先移植提交3rd會發生「合併衝突」,須要手動解決:
Git應用詳解第九講:Git cherry-pick與Git rebasecode

image-20200418220823727
經過vi test.txt查看發生合併衝突的test.txt文件:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200408123432173
能夠發現master分支上initial commit提交中的文件test.txt「直觀上」並不與提交3rd中的test.txt衝突,以下圖所示:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200408123754034
可是爲何會發生合併衝突呢?緣由在於「三方合併原則」:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200408143344853
如上圖所示,當想要將dev中的提交E與master分支的提交B合併時,首先要找到B和E的公共父節點A,在A的基礎上根據B和E進行三方合併;

瞭解了三方合併原則後就能解釋上面發生合併衝突的緣由了:

因爲提交3rd是基於提交2nd建立的,所以3rd中保留了2rd中對文件的操做記錄;

若是直接將3rd拼接到initial commit後面,就會失去提交2nd的記錄;

由此提交3rd就不能經過提交2nd找到公共提交節點init,這就會致使合併失敗;

因此,不管內容是否衝突,合併過程都會出現衝突:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418222100291
「解決方法」:手動合併三步曲:

首先,選擇要保留的內容,解決衝突:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200408133308462
而後,經過git add將修改信息歸入暫存區:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200408133412891
最後,經過git commit提交修改信息:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200418222349351
完成後查看master分支的提交歷史:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418222512780
能夠看到解決衝突,手動合併後,成功完成了整個cherry-pick過程。而且新增的提交是手動合併時進行的提交,而不是直接複製的提交3rd:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418222844236
2.第二步
此時兩分支的狀態爲:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418223143850
接下來就要刪除dev分支上錯誤的兩次提交2nd和3rd,至關於版本回退;可使用三種方法:revert、reset和checkout,這裏演示checkout和reset兩種方法。

使用checkout
首先切換到dev分支,而後經過如下指令切換到提交initial commit:

//dd703是提交initial_commit的SHA1值
git checkout dd703

此時該節點處於遊離狀態:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418223451519
而後再刪除dev分支:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418223548734
因爲以前修改的dev分支沒有與master進行合併,因此刪除時須要使用參數-D強制刪除。

刪除後,剩下master分支與遊離提交。此時再經過如下指令將遊離的節點設置爲dev分支便可:

git checkout -b dev

Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418223939367
由此經過"「偷天換日」"的方式使dev分支回到了錯誤提交前的狀態;

使用reset
因爲使用checkout只是移動了HEAD指針,沒移動dev分支指針,因此會出現遊離提交節點;而reset會同步移動HEAD和dev分支指針,不會形成這樣的問題。因此這裏使用reset進行版本回退會簡單不少:

git reset --hard dd703

Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418224610750
2、
git rebase

簡介
首先,rebase有兩個意思:「變基」、「衍合」,即變換分支的參考基點。默認狀況下,分支會以分支上的第一次提交做爲基點,以下圖所示master分支默認以提交1st做爲基點:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200409151236167
若是以提交4th做爲master分支的基點,master分支就會變爲:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200409151428243
這個變化基點的過程就稱之爲變基(rebase);

rebase與merge十分類似,不過兩者的工做方式有着顯著的差別。好比:將A和B兩分支進行合併:

在A分支上執行git merge B,表示的是將B分支「合併到」A分支上;
而在A分支上執行git rebase B,則表示將A分支經過變基「合併到」B分支上;
3、
merge

rebase

1.採用merge合併分支
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200408232708342
如今有兩個分支origin和mywork,若是想要將origin分支「合併到」mywork分支上。根據三方合併原則,須要在c四、c6和它們的公共父提交節點c2的基礎上進行合併:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200408232523880
合併後產生一次新的提交c7,該提交有兩個父節點c4和c6。具體的合併方式爲:若是沒有衝突git就會自動採用Fast-forward方式進行合併,有衝突就解決衝突再進行手動合併。

2.採用rebase合併分支
因爲是mywork分支須要變基合併到origin分支上,因此首先切換到mywork分支(注意這裏與採用merge方法時所在的分支相反):

git checkout mywork

再進行合併:

git rebase origin

合併後的結果爲:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200408232225944
「注意」:被合併的分支origin「保持不動」,而合併它的分支mywork將本身的提交做爲補丁(patch)一個個應用(applying)到分支origin指向的提交後面;

在這個過程當中git會自動建立c5'和c6'。原來的c5和c6就沒用了,會被git gc回收。合併後分支mywork的提交記錄變成了一條直線:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200408231936193

也就是說:rebase會將被合併分支(mywork)上的提交應用到合併分支(origin)上,而且修改被合併分支(mywork)的提交記錄。


4、
rebase
原理分析

如圖所示,master和dev分支都以提交節點A爲基準點:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418232253571
若是dev分支想要變換A這個基準點,那麼:

「第一步」:切換到dev分支上;

「第二步」:執行git rebase master,過程以下;


上述命令中rebase參數後面指定的就是變動後的基準點:

若是是分支,如master,基準點爲該分支的最新提交節點,也就是C;
若是是一個commit_id,基準點爲該commit_id對應的提交節點;

1.基準點爲分支
沿用以上模型:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418232806243
首先,將dev分支上除了基準點A外的全部節點複製一份,即D'和E',做爲補丁備用,並將分支dev指向新基準點C:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200418232419176
而後,按原來dev上的節點順序(D->E)將補丁應用(Patch Applying)到新基準點C後面,並同時改變分支dev指向:
「追加補丁D'」:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418232650653
每次向新基準點應用補丁時,都會出現「三個選項」:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418232951097
git rebase --continue
該選項表示:解決了合併衝突後,繼續應用剩餘補丁E':
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418233223765
git rebase --skip
該選項表示:跳過當前補丁,繼續應用下一個補丁:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418233400640
若是一直執行該選項,直到應用完分支dev上的補丁,結束rebase後,兩分支的狀態爲:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200418233514562
git rebase --abort
該選項表示:終止rebase操做,回到執行rebase指令前的狀態:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200418233837513
2.基準點爲提交
過程詳解
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200409184756113
如圖所示,若將提交節點B做爲基準點,在當前test分支上執行:

git rebase 3ccc8

會直接將原來的節點C和D應用到新基準點B後,至關於沒有發生變化,這個變基的過程爲:

首先,將基準點和test分支指向改變爲節點B,並將test分支上基準點日後的提交節點做爲補丁:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200409195531185
而後,按順序將補丁C和D應用到新基準點B後面:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200409202803624
最後,test分支的狀態爲:
Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200409202843582
因此,直接執行git rebase 678e0不會有任何變化:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200409203900098
可是,咱們能夠經過在rebase中添加參數-i,進入rebase交互模式,這樣就能在rebase操做過程當中對特定的補丁進行一系列操做;

實戰演示

首先在test分支上進行了四次提交:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200409191637780
執行如下指令將test分支的基準點變爲提交節點B(678e0),並進行變基:

git rebase -i 678e0

執行該指令後,會進入vim編輯器:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200409192056322
能夠根據須要將pick參數,改變爲下面表明不一樣做用的參數;這樣就能夠對節點C和D進行不一樣的操做了。好比:

pick:默認參數,表示不對提交節點進行任何操做,直接應用原提交節點。不建立新提交;
reword:應用複製事後的原提交節點,可是能夠編輯該節點的提交信息。經過這個參數,能夠修改特定提交的提交信息。會建立新的提交;
edit:應用複製事後的原提交節點,會在設置了該參數的補丁上中止rebase操做。待修改完該補丁後,調用git rebase --continue繼續進行rebase。會建立新的提交;
squash:將新基點後面的所有提交節點進行合併,也就是將這裏的C和D兩個節點進行合併。會建立新的提交;
還有其餘參數這裏就不一一介紹了。
此次直接使用默認的pick參數,經過:wq保存並退出vim編輯器,完成rebase操做:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200409194956051
執行rebase操做前:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200409191637780
能夠看到當新基準點爲特定提交時:

在rebase的過程當中使用默認參數pick,並不會像當新基準點爲分支時那樣建立新的提交;
而一旦使用其餘參數(如reword)對補丁進行了修改,就會建立新的提交;
5、
rebase
注意事項

不要對master分支執行rebase,不然會引發不少的問題(master必定是遠程共享的分支);

通常來講,執行rebase的分支都是本身的本地分支,千萬不要在與其餘人共享的遠程分支上使用rebase;

這不難理解,遠程分支上的代碼可能已經被其餘人克隆到本地了,若是經過rebase修改了遠程分支的提交歷史,這樣其餘人每次拉取代碼到本地時,就都須要進行復雜的合併。

因此,本地的非master分支合併時推薦使用git rebase,其餘分支的合併推薦使用git merge;

「注意:git merge和git rebase的顯著區別是,前者不會修改git的提交記錄,然後者會!」

6、
rebase
應用場合

1.合併分支
因爲git merge採用的是「三方合併」的原則,沒有公共提交節點就沒法進行合併,此時能夠採用rebase進行合併。以下圖所示:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200411205020369
本地master與遠程master分支沒有公共提交節點,沒法採用git merge合併。可採用rebase進行合併:

//origin/master表明着遠程master分支
git rebase origin/master

合併後本地master分支的狀態爲:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200411205034662
2.修改特定提交
如下狀況就適合使用rebase來解決,當回退版本並進行修改時:

好比在master分支上進行了3次提交:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419174116301
回退到第二次提交2nd,並對提交信息進行修改:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419174313522
當咱們回到原來的第三次提交3rd時,會發現以前的修改並無被保存:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419174404816
此時可使用rebase,將提交1st做爲新的提交節點(正如第四大點講解的)。首先執行:

git rebase -i 5ab3f

經過添加參數-i進入交互模式,將提交2nd默認的pick參數修改成reword參數:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419174618553
保存並退出後,進入修改提交信息界面:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419174829838
保存並退出,由此完成修改:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419174935598
7、
rebase
實戰

爲了演示,額外建立兩個分支dev和test,分別在兩個分支上進行兩次提交:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419150528809
它們有一個共同的父節點提交節點init,此時本地倉庫的狀態以下:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419154602095
因爲要對test分支進行變基,從而「合併到」dev分支上,因此須要先切換到test分支上,這與merge操做是相反的;

隨後在test分支上執行以下命令對該分支進行變基:

git rebase dev

該指令翻譯過來就是:我test 分支,如今要從新定義個人基準點,即便用 dev 分支指向的提交做爲我新的基準點。過程以下:

首先,將test分支上的提交(補丁)tes1應用到「新基準點」dev2尾部,出現了合併衝突:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419151735485
查看狀態,發現test分支變基過程當中的新基準點正是dev分支指向的提交361be,即提交節點dev2:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419152146120
如圖所示,此時有三個選項:

「選項一」:git rebase --abort:表示終止rebase操做,恢復到操做前;

「選項二」:git rebase --skip:表示丟棄當前test分支的補丁,若是一直執行該選項,變基完成後,兩分支的狀態以下所示:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419154352758
即此時test分支與dev分支上具備相同的文件:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419155017163
而且test分支上的提交記錄被改變爲了dev分支上的提交記錄:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419153242071
這就是一直執行選項git rebase --skip,丟棄所有test分支補丁的結果:

「選項三」:git rebase --continue:解決衝突,手動合併後,繼續變基;

在dev分支上新增兩次提交dev3和dev4:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419153831132
切換回test分支一樣新增兩次提交tes3和tes4:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419154032932
此時兩分支的狀態爲:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419184609655
隨後在test分支上執行git rebase dev,在處理test分支上的第一個補丁tes3時出現衝突:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419155615321
打開衝突文件test.txt,手動解決衝突:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419155711866
刪除四、七、9行:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419155812608
解決衝突後,執行git add將對文件`test.txt的修改操做歸入暫存區,標識已解決衝突:


「注意」:這裏並不須要進行一次提交,繼續執行rebase操做便可;


Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419160220448
隨後再執行git rebase --continue,繼續處理test分支的下一個補丁(變基):
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419160305488
rebase結束後,查看test分支的提交記錄:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419160412284
能夠發現修改了test分支的提交歷史,達到了預期的合併效果。

而且,此時test分支上的tes3與tes4兩次提交的SHA1值與執行rebase前這兩次提交的SHA1值是不同的:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419160741986
這也就驗證了,git在rebase過程當中會自動建立提交節點的結論。此時dev分支與test分支的狀態以下所示:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419161342071
若是在dev分支上執行git merge test,採用的應當是Fast-forward方式:

Git應用詳解第九講:Git cherry-pick與Git rebase
image-20200419161456806
使用gitk能夠更加直觀地表示這一狀態:
Git應用詳解第九講:Git cherry-pick與Git rebase

image-20200419161529226

細心的你可能已經發現了,rebase與cherry-pick十分相似。只不過cherry-pick不會修改分支提交記錄,而rebase會。


8、
merge

rebase
的選擇

使用rebase時要遵循rebase的黃金法則:永遠不要在公共分支上使用rebase。公共分支能夠理解爲master分支。因爲rebase會重寫分支提交記錄,所以會給項目的回溯帶來危險。如下爲它與merge的區別:

merge是一個合併操做,使用git merge提交歷史會出現分叉,顯得不是那麼簡潔。可是,它的好處在於不會修改任何一次提交,會完整地將全部的提交都保存下來,方便回溯。「而且只能合併有公共提交節點的分支;」

rebase是沒有合併操做的,它只是將當前分支所作的修改複製到了目標分支的最後一次提交上。因此能夠不受「三方合併原則」約束,合併無公共提交節點的分支;

使用rebase會修改提交歷史,獲得的分支提交歷史更加整潔。就好像寫書,只會出版最終版本,以前的書稿並不會出版。可是,必定要注意「不能在共享的分支上使用rebase」。

兩者都是很強大的分支整合命令,使用哪一個由具體情境決定。

9、
rebase

reset

revert

這三個指令的名字很像,容易混淆,下表對比了它們的用途以及區別:
Git應用詳解第九講:Git cherry-pick與Git rebase

10、
git
最佳實踐

學到這裏就能夠徹底理解使用git將本地倉庫文件推送到遠程倉庫的通常步驟了:

「第一步」:建立本地倉庫:

git init
「第二步」:添加用戶信息:

git config --global user.name '張三'
git config --global user.email 'zhangsan@git.com'
「第三步」:添加遠程倉庫地址:

git remote add origin https://www.github.com/example
「第四步」:修改文件;

「第五步」:將工做區中的文件歸入暫存區:

git add .
「第六步」:將暫存區中的文件提交到版本庫:

git commit -m '註釋'
「第七步」:與遠程倉庫進行同步:

git pull --rebase origin master
「第八步」:創建本地分支與遠程分支的聯繫,並進行推送:

git push -u origin master

相關文章
相關標籤/搜索