標籤:

Git由淺入深之細說變基(rebase)

在上一篇,詳細介紹了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變基的作用也是整合變更,首先在待合併分支執行變基,最後還是歸於分支合併,但是在這個過程與直接合併分支還是有差別,正如本文的例子,可以看出變基會保留分支的提交歷史,但是是通過將其併入主線保存的,之後關於該分支開發的具體歷史及關係,已經被遮蓋了,即歷史已被休整,而我們通過直接合併分支方式整合變更時,分支的提交記錄依然可以以分支的形式獨立存在,歷史未被修改。

選擇變基還是合併

上一節,得出結論:

變基會修整歷史,然後將分支歷史併入主線,可以理解成美化過的歷史,而合併則可以不修改歷史,讓分支歷史依然獨立存在,可以看作原始的歷史。

所以選擇變基還是合併,看具體需求,你只是想要一個清晰,明了的歷史,並不關係歷史的具體來源,你可以首選變基,但是如果你想比較清楚地了解項目不同階段的原始歷史,你可以選擇直接合併。

一個原則

說到最後,還有一個不得不提的原則:

永遠不要對已經推到主幹分支伺服器或者團隊其他成員的提交進行變基,我們選擇變基還是合併的範圍應該在自己當前工作範圍內。

[更好的閱讀體驗請移步](Git由淺入深之細說變基(rebase) - 熊建剛的博客)

推薦閱讀:

Git 工作流指南
Oh shit, Git! 快速解決Git最常見問題
Git 初學者攻略
Git由淺入深之存儲原理
如何搭建私有可協作的 Git 伺服器

TAG:Git |