在上一篇,詳細介紹了Git分支管理,最後一節介紹了Git變基及其與合併的區別,限於篇幅,並未對變基展開介紹,實在是因為關於Git變基需要闡述的內容頗多,而且並不是新手能徹底掌握的,於是計劃單列一篇,由淺入深,詳細剖析,若有失誤之處,望看官包容,指正。
變基(rebase)
rebase,有墊底,基底的意思,Git rebase
我們稱它為Git變基,即在當前分支外另一點上重新應用當前分支的提交歷史,變基是Git整合變更的一種方式,另一種方式是什麼呢(當然是合併了)?變基的基礎使用已經在上一篇中提到,此處就不重複了,下面會闡述更深入或者說對於初學者更不常用的Git變基知識點。
多主題分支變基(git rebase –onto)
無論曾經,還是未來,只要你一直使用Git,早晚會遇到這樣一種情況:從主幹切出了某一分支issue1
,進行了一些提交後,有另一個需求,我們在issue1
分支切出新分支issue2
,進行了提交,這期間其他成員對master
主幹分支進行了更新,結構圖如下:
現在,issue2分支開發完,我們需要將其變更併入主線,但是issue1分支的變更還是繼續保持獨立,如果直接進行簡單變基,cec214,d4eb57
也將被併入主線,這不是我們想要的結果,我們只希望將issue2分支上進行的變更和提交併入主線,即5f3776,ac5c08
,這時需要使用git rebase --onto
指令:
提交歷史圖:
分支結構圖:
可以看出,執行--onto
變基後,在主線上複製了issue2
分支的所有提交物件,此處所謂複製,是在主線建立新提交物件,而其提交內容及備註複製自issue2
分支的提交物件,對於如下:
git rebase –onto
上例使用了git rebase --onto 變基目標分支master 變基過渡分支issue1 變基當前分支issue2
指令,此指令解釋如下:
- 變基當前分支,即當前執行變基的分支issue2;
- 變基目標分支,即當前執行變基分支的變基指向分支master;
- 變基過渡分支,即當前執行變基分支過濾提交物件的目標分支issue1(當前分支從過渡分支切出);
- 取出issue2分支上進行的所有提交物件,以補丁(patches)形式在主線master分支上重新應用,建立新提交
物件,然後將分支指標移動到最後一個新建立的提交物件。
需要注意的是此時master分支指標並沒有更新到新提交物件,我們需要手動進行合併:
合併後提交歷史圖:
此時master分支和issue2分支指標均指向主線最新提交物件749a39
,執行合併指令時輸出的第一行資訊指明當前主線分支從624c26
推進到749a39
,第二行Fast-forward
說明是快速推進合併方式。
變基與衝突
接下來,如果issue1分支開發測試完成,可以繼續對該分支進行變基,合併:
如圖中紅線表明,變基合併檔案時發生衝突,因為兩個分支對同一檔案同一部分進行了變更,需要我們手動處理衝突,開啟該檔案,處理掉衝突,然後繼續執行變更:
注:注意此處紅線標註處No changes -
,為什麼會說沒有變更呢?因為在處理變基衝突時,我把此次提交的變更刪除了,該檔案沒有變化,後文你會發現這樣處理的意圖。
如上圖示紅處顯示REBASE 1/3
,說明變基時存在多個提及物件需整合變更,需要多次處理衝突,這時,我們就需要使用git rebase --skip
指令:
執行完git rebase --skip
後,如圖紅線圈處REBASE 2/3``,說明第一個提交物件整合完成,再看紅線下劃線標註處第一行
Applying: second edition of test.md“`指明當前繼續變基時處理的提交物件資訊,第二行指明衝突所在檔案,首先處理衝突,然後繼續執行變基:
如圖,需要注意的有兩點:
- 在執行
git rebase --continue
指令時,需要使用git add
指令標記衝突已解決(和SVN一樣有這個過程); - 繼續變基完成時,我們看到輸出了兩行
Applying: 提交物件備註資訊
,這裡兩行是重點。
檢視提交歷史:
目前,分支結構如下:
issue1分支併入主線,其提交歷史也全部併入主線,提交物件複製關係如下:
我們發現,在issue1分支上有三次提交,然而併入主線只建立(複製)了兩個提交物件,這是因為我們在處理cec214
提交物件的變基衝突時,把此提交物件的變更刪除了,即未發現變更,Git未將此提交物件併入主線。
接下來,可以在master分支合併issue1分支。
互動式變基(rebase –interactive)
這一節要介紹的是互動式變基,指令為git rebase -i
或git rebase --interactive
,使用該指令可以修改提交歷史,其後引數可以是某一特定提交物件ID或執行特定提交物件的指標,將輸出該提交物件之後的所有提交物件(不包括該提交物件),如HEAD~
表明輸出當前分支最新一次提交物件,HEAD~~
表明輸出當前分支的最新的兩次提交物件。
HEAD~~
如下,在issue1分支執行指令git rebase -i HEAD~~
,會進入編輯模式:
如圖列出了HEAD~~指向的提交物件之後的所有提交物件,每一行之前預設是pick
標誌,表示該提交物件正在使用,我們可以修改該標記值,隨後Git根據標記值執行不同操作,標記值與操作對應關係如下:
- pick(p),表明正在使用;
- reword(r),表明仍然使用該提交物件,但是需修改提交資訊;
- edit(e),使用該提交物件,但是不合並提交物件;
- squash(s),使用該提交物件,但是將此提交與上一次提交物件合併;
- fixup(f),同squash值,但是丟失此次提交的日誌資訊;
- exec(x),後接特定指令碼,儲存後將執行該指令碼;
- drop(d),移除該提交物件
reword
當我們把第二個提交前面的pick
修改成reword
,儲存退出編輯模式,輸出如下:
我們可以繼續編輯提交物件資訊,然後儲存退出編輯模式,這裡我修改了提交物件備註資訊,如下圖紅圈處:
exec
如果繼續執行git rebase -i HEAD~~
指令,我們再使用exec
模式:
儲存退出編輯模式後,輸出:
-i 提交物件ID
當然除了使用HEAD
指標做引數,git rebase -i
後也可以接具體提交物件ID,如0f7de9
,將輸出該提交物件之後的所有提交物件(不包括該提交物件):
如上圖,輸出了0f7de9
提交物件之後的所有提交物件,此指令等同於git rebase -i HEAD~
。
-i origin/master
比較特殊的一個引數是origin/master
,使用git rebase -i origin/master
,可以獲取最後一次從origin遠端倉庫拉取(pull)或推送(push)之後的所有提交。
變基的影響
總結下來,Git變基的作用也是整合變更,首先在待合併分支執行變基,最後還是歸於分支合併,但是在這個過程與直接合並分支還是有差別,正如本文的例子,可以看出變基會保留分支的提交歷史,但是是通過將其併入主線儲存的,之後關於該分支開發的具體歷史及關係,已經被遮蓋了,即歷史已被休整,而我們通過直接合並分支方式整合變更時,分支的提交記錄依然可以以分支的形式獨立存在,歷史未被修改。
選擇變基還是合併
上一節,得出結論:
變基會修整歷史,然後將分支歷史併入主線,可以理解成美化過的歷史,而合併則可以不修改歷史,讓分支歷史依然獨立存在,可以看作原始的歷史。
所以選擇變基還是合併,看具體需求,你只是想要一個清晰,明瞭的歷史,並不關係歷史的具體來源,你可以首選變基,但是如果你想比較清楚地瞭解專案不同階段的原始歷史,你可以選擇直接合並。
一個原則
說到最後,還有一個不得不提的原則:
永遠不要對已經推到主幹分支伺服器或者團隊其他成員的提交進行變基,我們選擇變基還是合併的範圍應該在自己當前工作範圍內。