rebase

注意事項(xiàng)

  1. 參考1,圖片也來源于該鏈接;參考2。

  2. 變基操作的 實(shí)質(zhì)是丟棄一些現(xiàn)有的提交,然后相應(yīng)地新建一些內(nèi)容一樣但實(shí)際上不同的提交

  3. 使用變基操作的原則是:只對尚未推送或分享給別人的本地修改執(zhí)行變基操作,從不對已推送至別處的提交執(zhí)行變基操作。這是因?yàn)樽兓鶗?huì)丟棄一些提交。如果這些提交已經(jīng)分享給別人,那么再執(zhí)行變基操作后將導(dǎo)致別人需要整合。

  4. rebase 不會(huì)合并提交結(jié)點(diǎn)。假設(shè) rebase 之前有三個(gè)結(jié)點(diǎn), rebase 時(shí)會(huì)生成三個(gè)相應(yīng)的結(jié)點(diǎn),并不會(huì)將三個(gè)結(jié)點(diǎn)合并成一個(gè)。

  5. 變基命令是在推送前清理提交使之整潔的工具,千萬不要對已經(jīng)共享的結(jié)點(diǎn)進(jìn)行變基操作。

與 merge 比較

rebase 與 merge 一樣,都用于分支合并。

  1. merge 執(zhí)行的是三方合并。merge 會(huì)把兩個(gè)分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)進(jìn)行三方合并,合并的結(jié)果是生成一個(gè)新的快照(并提交)。如下圖:

    merge 合并

  2. rebase (變量)會(huì)將某一分支上的所有修改都移至另一分支上。對上圖來說,你可以提取在 C4 中引入的補(bǔ)丁和修改,然后在 C3 的基礎(chǔ)上應(yīng)用一次。

    rebase 合并 圖二

    當(dāng)變基成功后, 再切回 master 分支,進(jìn)行一次快進(jìn)合并即可。

    $ git branch
    * d2
      dev
    
    $ git rebase dev
    First, rewinding head to replay your work on top of it...
    Applying: udev2
    
    $ git checkout dev
    Switched to branch 'dev'
    
    $ git merge d2
    

    通過 branch 命令看出當(dāng)前處在 d2 分支中。通過 rebase 將 d2 變基到 dev 分支中。在切換回 dev 分支后合并 d2 分支。

  3. merge 時(shí)主分支會(huì)有并行結(jié)構(gòu),即某個(gè)結(jié)點(diǎn)可能會(huì)有多個(gè)父結(jié)點(diǎn)。但使用 rebase 時(shí),所有的結(jié)點(diǎn)都是串行的。

  4. rebase 時(shí),rebase 后跟的是目標(biāo)基底,會(huì)將當(dāng)前分支的修改應(yīng)用到目標(biāo)基底分支上。要注意的是:rebase 后,目標(biāo)基底指向并不會(huì)發(fā)生變化,仍需要 git merge 進(jìn)行快進(jìn)合并。


原理

以圖二來說

  1. 首先找到這兩個(gè)分支(即當(dāng)前分支 experiment、變基操作的目標(biāo)基底分支 master)的最近共同祖先 C2。

  2. 然后對比當(dāng)前分支相對于該祖先的歷次提交,提取相應(yīng)的修改并存為臨時(shí)文件。

  3. 然后將當(dāng)前分支 ( experiment ) 指向目標(biāo)基底 C3。

  4. 最后以此將之前另存為臨時(shí)文件的修改依序應(yīng)用,得到新的提交結(jié)點(diǎn) C4‘,并將當(dāng)前分支指向 C4'。

由于新的提交結(jié)點(diǎn)以目標(biāo)基底為父結(jié)點(diǎn),所以切換到目標(biāo)基底后進(jìn)行 merge 操作時(shí),執(zhí)行的是快進(jìn)合并。


--onto

剪切指定范圍內(nèi)的提交結(jié)點(diǎn),并在指向的分支上對這些節(jié)點(diǎn)執(zhí)行變基操作。

其命令格式為:

git rebase --onto base from to

其含義是:將 (from, to] 范圍內(nèi)的所有提交結(jié)點(diǎn)在 base 指向的結(jié)點(diǎn)之后重建

假設(shè)現(xiàn)在提交記錄為 ( 來源于參考 2 ):

                            H---I---J topicB
                           /
                  E---F---G  topicA
                 /
    A---B---C---D  master

執(zhí)行 git rebase --onto master topicA topicB 后,其結(jié)果為:

                 H'--I'--J'  topicB
                /
                | E---F---G  topicA
                |/
    A---B---C---D  master

注意上圖提交后的結(jié)果:master 的指向并沒有發(fā)生變化,但變基生成的新提交結(jié)點(diǎn)以 master 為祖先結(jié)點(diǎn)。因此,master 與 topicB 合并時(shí),會(huì)執(zhí)行快進(jìn)合并 —— 直接將 master 的指向 J’ 結(jié)點(diǎn)即可。

另外,執(zhí)行 rebase 后,原有的 J 結(jié)點(diǎn)沒有任何指針指向它。為避免原有的結(jié)果丟失,可以在 topicB 處新建一分支,然后再執(zhí)行 rebase。


沖突的解決

在進(jìn)行變基操作時(shí),如果出現(xiàn)文件沖突,解決步驟如下:

  1. 手動(dòng)解決沖突后,通過 git add <filename> 暫存沖突文件。注意:這步不需要進(jìn)行 commit。

  2. 使用 git rebase --continue 繼續(xù)本次變基操作;或使用 git rebase --abort 放棄本次變基操作。

    $ git rebase dev d
    # 此處出現(xiàn)沖突時(shí)的文字提示,也可以通過 git status 進(jìn)行查看
    
    $ git status
    rebase in progress; onto bc4cd42
    You are currently rebasing branch 'd' on 'bc4cd42'.
      (fix conflicts and then run "git rebase --continue")
      (use "git rebase --skip" to skip this patch)
      (use "git rebase --abort" to check out the original branch)
    
    Unmerged paths:
      (use "git reset HEAD <file>..." to unstage)
      (use "git add <file>..." to mark resolution)
    
        both modified:   a.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
    
    $ vim a.txt 
    $ git add a.txt 
    $ git rebase --continue
    

--continue 與 --abort

沖突解決中,最后使用 continue ,表示繼續(xù)本次變基操作。

也可以使用 git rebase --abort 表示放棄本次 rebase 操作。

$ git rebase dev d
# 沖突的提示

$ git branch
* (no branch, rebasing d)
  d
  dev
  master
  
$ git rebase --abort
$ git branch
* d
  dev
  master

可以發(fā)現(xiàn)出現(xiàn)沖突時(shí),Git 類似于新建了一個(gè)匿名分支。當(dāng)使用 --abort 選項(xiàng)時(shí),Git 會(huì)直接切回原分支,那么該匿名分支將丟失,也就是放棄了本次變基操作。


-i 選項(xiàng)

以交互模式進(jìn)行變基操作。

  1. 對指定范圍內(nèi)的提交進(jìn)行變基,相當(dāng)于修改指定結(jié)點(diǎn)范圍內(nèi)的所有提交。

  2. 指定的范圍是前開后閉。如:

    git rebase -i HEAD~3 HEAD~1
    

    會(huì)以 HEAD~3 為基底,對 HEAD2,HEAD1 進(jìn)行變基操作。

    因?yàn)槭亲兓?,所以在?zhí)行完命令之后,當(dāng)前分支會(huì)指向新生成的結(jié)點(diǎn),原有的結(jié)點(diǎn)將丟失。因此,如果還要保留原有的結(jié)點(diǎn)指向,可以在執(zhí)行 rebase 之前新建一個(gè)指針,或在執(zhí)行命令之后通過 git reflog 找到原有的結(jié)點(diǎn)的 sha1 值,并建立指向(如分支,標(biāo)簽等)。

使用上述命令后,會(huì)出現(xiàn)如下界面(這個(gè)界面是 vim 格式,可以編輯保存):

pick e81ab54 add test fun
pick a686383 update test func

# Rebase 3a47216..a686383 onto 3a47216 (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

前兩行未加 #,表示未注釋內(nèi)容。在編輯保存之后, git 會(huì)讀取未注釋行內(nèi)容,并根據(jù)內(nèi)容對每一次提交結(jié)點(diǎn)進(jìn)行操作。

提交結(jié)點(diǎn)按從舊到新依次排序,即最上面的結(jié)點(diǎn)最先提交。另外,可以 調(diào)整行的順序,用以修改對不同結(jié)點(diǎn)進(jìn)行變基操作的順序。

每一行代表一次提交。行前面的 pick 表示要對此次提交進(jìn)行的操作,操作后的 sha1 值表示此次提交對應(yīng)的 commit 對象,最后是本次提交時(shí)輸入的提交信息。

其中 e81ab54 指向的是 HEAD~2,a686383 指向的是 HEAD~1,而 3a47216 指的是 HEAD~3。

第一行注釋中 3a47216..a686383 指包含在 a686383 中,卻不包含中 3a47216 中的結(jié)點(diǎn),也就是 HEAD~2 與 HEAD~1,即 2 個(gè)結(jié)點(diǎn)。

假設(shè) 刪除了某一結(jié)點(diǎn)所對應(yīng)的行,則該結(jié)點(diǎn)不會(huì)進(jìn)行變基操作 —— 即放棄了這個(gè)結(jié)點(diǎn)對應(yīng)的修改。

常用操作

操作 含義
pick 對結(jié)點(diǎn)進(jìn)行變基
reword 對結(jié)點(diǎn)進(jìn)行變基,但可以重新編輯提交信息
edit 將變基操作暫停在指定的結(jié)點(diǎn)處,可以進(jìn)行 修改信息,拆分結(jié)點(diǎn) 等操作
squash 將本結(jié)點(diǎn)壓縮到前一個(gè)結(jié)點(diǎn)中
  1. 使用 reword 后,會(huì)在保存變基指令后重新彈出界面,在該界面中輸入指定結(jié)點(diǎn)的提交信息。

  2. edit 類似于斷點(diǎn),變基進(jìn)行到斷點(diǎn)時(shí)會(huì)自動(dòng)暫停,直到通過 git rebase --continue 命令放行為止。使用 edit 操作后,界面如下:

    $ git rebase -i HEAD~3 HEAD~1 
    Stopped at e81ab54bae45a53ea681ba0096085833bb5c6843... add test fun
    You can amend the commit now, with
    
        git commit --amend
    
    Once you are satisfied with your changes, run
    
        git rebase --continue
    
    $ git commit --amend
    [detached HEAD 350071f] add test fun2
     Date: Thu Mar 29 22:00:23 2018 +0800
     1 file changed, 3 insertions(+), 4 deletions(-)
    

    當(dāng)前變基操作暫定在 e81ab 結(jié)點(diǎn)上。此時(shí)可以修改本次結(jié)點(diǎn)的提交信息,還可以將本次結(jié)點(diǎn)拆分成多個(gè)。注意:修改完成之后,需要運(yùn)行 git rebase --continue 放行;如果不需要修改可以直接使用 git rebase --continue 放行。

    • 直接使用 git commit --amend 修改該結(jié)點(diǎn)提交信息。

    • 也可以重置暫存區(qū),然后將修改分多次提交。如:

      $ git reset HEAD^
      $ git add README
      $ git commit -m 'updated README formatting'
      $ git add lib/simplegit.rb
      $ git commit -m 'added blame'
      $ git rebase --continue
      

      上述命令首先重置暫存區(qū)(git reset HEAD^),然后將 README 與 lib/simplegit.rb 分兩次提交,最后通過 git rebase --continue 放行斷點(diǎn),繼續(xù)進(jìn)行變基操作。

      拆解結(jié)束后,原來的一個(gè)提交結(jié)點(diǎn)會(huì)被拆成兩個(gè),而原來的結(jié)點(diǎn)將不存在。

  3. 使用 squash 操作時(shí),第一行不能使用 squash。因?yàn)榈谝恍袥]有前結(jié)點(diǎn),無法合并。使用 squash 操作后,通過 git log 查看提交歷史如下:

    $ git log -4
    commit 069e62223017380c9ec46565acb0340f99af82c3
    commit 3a4721694a888a876d062ac90bca03115b3394b7
    
    $ git log -4
    commit a6863836d1d54c00035511824758a8da6dfed83b
    commit e81ab54bae45a53ea681ba0096085833bb5c6843
    commit 3a4721694a888a876d062ac90bca03115b3394b7
    

    第一個(gè)命令是使用 squash 時(shí)的提交記錄,第二個(gè)是不使用 squash 時(shí)的提交記錄。可以看出,使用 squash 時(shí),Git 會(huì)將兩個(gè)結(jié)點(diǎn)合并成一個(gè)。因此,上面的 log 在 3a47216 前只有一條記錄,而下面的卻有兩條記錄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容