注意事項(xiàng)
變基操作的 實(shí)質(zhì)是丟棄一些現(xiàn)有的提交,然后相應(yīng)地新建一些內(nèi)容一樣但實(shí)際上不同的提交。
使用變基操作的原則是:只對尚未推送或分享給別人的本地修改執(zhí)行變基操作,從不對已推送至別處的提交執(zhí)行變基操作。這是因?yàn)樽兓鶗?huì)丟棄一些提交。如果這些提交已經(jīng)分享給別人,那么再執(zhí)行變基操作后將導(dǎo)致別人需要整合。
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è)。
變基命令是在推送前清理提交使之整潔的工具,千萬不要對已經(jīng)共享的結(jié)點(diǎn)進(jìn)行變基操作。
與 merge 比較
rebase 與 merge 一樣,都用于分支合并。
-
merge 執(zhí)行的是三方合并。merge 會(huì)把兩個(gè)分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)進(jìn)行三方合并,合并的結(jié)果是生成一個(gè)新的快照(并提交)。如下圖:
merge 合并 -
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 分支。
merge 時(shí)主分支會(huì)有并行結(jié)構(gòu),即某個(gè)結(jié)點(diǎn)可能會(huì)有多個(gè)父結(jié)點(diǎn)。但使用 rebase 時(shí),所有的結(jié)點(diǎn)都是串行的。
rebase 時(shí),rebase 后跟的是目標(biāo)基底,會(huì)將當(dāng)前分支的修改應(yīng)用到目標(biāo)基底分支上。要注意的是:rebase 后,目標(biāo)基底指向并不會(huì)發(fā)生變化,仍需要
git merge進(jìn)行快進(jìn)合并。
原理
以圖二來說
首先找到這兩個(gè)分支(即當(dāng)前分支 experiment、變基操作的目標(biāo)基底分支 master)的最近共同祖先 C2。
然后對比當(dāng)前分支相對于該祖先的歷次提交,提取相應(yīng)的修改并存為臨時(shí)文件。
然后將當(dāng)前分支 ( experiment ) 指向目標(biāo)基底 C3。
最后以此將之前另存為臨時(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)文件沖突,解決步驟如下:
手動(dòng)解決沖突后,通過
git add <filename>暫存沖突文件。注意:這步不需要進(jìn)行 commit。-
使用
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)行變基操作。
對指定范圍內(nèi)的提交進(jìn)行變基,相當(dāng)于修改指定結(jié)點(diǎn)范圍內(nèi)的所有提交。
-
指定的范圍是前開后閉。如:
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)中 |
使用 reword 后,會(huì)在保存變基指令后重新彈出界面,在該界面中輸入指定結(jié)點(diǎn)的提交信息。
-
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)將不存在。
-
使用 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 前只有一條記錄,而下面的卻有兩條記錄。

