概述
在之前总结分支相关内容时说道,合并两个分支的提交可以使用git merge,然而除了这种方式之外,还有一种方式就是使用git rebase,这两种方式的最终结果都相同,但是合并历史却不同;git merge是将两个分支做一个三方合并(如果不是直接上游分支),这样一来,查看提交历史记录,可能会显得非常凌乱。git rebase则会将当前分支相对于基低分支的所有提交生成一系列补丁,然后放到基底分支的顶端,从而使得提交记录变称一条直线,非常整洁。git merge 和 git rebase 区别
假设现在本地仓库中有两个分支:master分支和branch1分支,提交历史用图来表示如下:现在要合并branch1到master分支,如果使用git merge则执行如下命令:
$ git checkout master
$ git merge branch1 合并后查看提交历史如下:$ git log --graph --pretty="oneline"
* fe8799e0aec30e388306883960b4cf438d3f1ec4 Merge branch 'branch1' |\ | * cf31255da6e84acc6f6840e3ceb0fd3129e2d73e UserA commit 3--branch1 | * 5c2d1c938f8e5f98dccaa0a5ab6222bd6b1cd75d UserA commit 2--branch1 * | 284aa3eb6c405411584d682a1387118fe92e4821 Usera commit master * | 967fca58deb914ad1cda9ff84291fd946045207d Usera commit master |/ * d989fc50530918b3b7b0ed68b31d6751c2302875 UserA commit 1 使用图来表示,本地仓库提交历史如下:$ git checkout branch1
$ git rebase master First, rewinding head to replay your work on top of it... Applying: UserA commit 2--branch1 Applying: UserA commit 3--branch1 Applying: Usera commit master Applying: Usera commit master 合并后查看提交历史如下:$ git log --graph --pretty="oneline"
* 6cf95c391ba7d43d0f5d95300130a43816af82c8 Usera commit master * 63def8a8740b9b3c9f6c09ae49ba72faa9446cf6 Usera commit master * 33049864f83a686bff9b2a2d8626427653a16f22 UserA commit 3--branch1 * 14ac1cac7357ccf35581c89e099793260264d3ea UserA commit 2--branch1 * d989fc50530918b3b7b0ed68b31d6751c2302875 UserA commit 1 使用图来表示,本地仓库提交历史如下:可以看到,现在branch1分支上相对于master分支的提交,提交到了master分支的顶端,如此一来整个提交记录保持在一条直线上。这就是git rebase。
rebase原理
git rebase <branch>的原理是:找到两个分支最近的共同祖先,根据当前分支(上例中branch1)的提交历史生成一系列补丁文件,然后以基地分支最后一个提交为新的提交起始点,应用之前生成的补丁文件,最后形成一个新的合并提交。从而使得变基分支成为基地分支的直接下游。rebase一般被翻译为变基。当branch1分支完成变基后,直接变成了master分支的下游了,这时切换到master分支,直接通过git merge即可将branch1分支的合并到master分支上:
$ git merge branch1
Updating ff7658d..d6168dc Fast-forward test.txt | 1 + 1 file changed, 1 insertion(+) 掌握了rebase基本原理后,接下来对该命令一些参数进行下总结。git rebase branchA branchB
首先会取出branchB,将branchB中的提交放在branchA的顶端,一般branchB为当前分支,可以不指定。假设当前本地仓库提交历史如下,且处于topic分支:
A---B---C topic
/ D---E---F---G master 此时我们使用git rebase将两个分支的提交合并到master分支的顶端:$ git rebase master
# 或者 $ git rebase master topic 此时,提交历史将变为:A'--B'--C' topic
/ D---E---F---G master git rebase --onto 如果要在合并两分支时需要跳过某一分支的提交,这时就可以使用--onto参数了。比如,假设当前本地仓库提交历史如下:A---B---E---F---G master
\ C---D---H---I---J next \ K---L---M topic 此时topic分支的上游分支是next分支,如果要将topic分支上的提交(K,M,L)跳过next分支,直接放到master分支上,就需要加上--onto参数:$ git rebase --onto master next topic
1 上述命令的意思是:取出topic分支,找出topic和next分支的共同祖先之后的提交,然后放在master分支上,执行后提交历史变为如下:A---B---E---F---G master
\ \ \ K'---L'---M' topic \ C---D---H---I---J next git rebase --continue/abort/skip 这三个命令分别表示:继续执行变基操作、终止变基、跳过某一文件继续进行。在rebase的过程中,有可能会出现文件冲突,比如:$ git rebase master
First, rewinding head to replay your work on top of it... Applying: [Description]:test1 Using index info to reconstruct a base tree... M test.txt Falling back to patching base and 3-way merge... Auto-merging test.txt CONFLICT (content): Merge conflict in test.txt error: Failed to merge in the changes. Patch failed at 0001 [Description]:test1 The copy of the patch that failed is found in: .git/rebase-apply/patchWhen you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort". $ 这种情况下,首先要解决冲突,解决冲突后可以选择继续执行rebase或者结束rebase,一般的做法为:$ git add filename
$ git rebase --continue 1 2 或者可以选择终止变基:$ git rebase --abort
1 或者跳过该patch:$ git rebase --skip
1 git rebase -i 该命令相比其他命令,使用频率要高得多。git rebase -i <commitid>可以进行交互式变基,相比于git rebase <branch>用来变基,它经常用来操作当前分支的提交,git会将<commitid>-HEAD之间的提交列在一个变基脚本中,每一笔提交根据用户设置的命令,会进行不同的操作,如修改提交信息、移除指定提交、合并两个提交为一个(压缩提交)、拆分提交等。如,要对最近4次提交进行重新操作,则:
$ git rebase -i HEAD~4
1 此时将会弹出如下形式的变基脚本:1 pick af98479 [Description]:test4
2 pick 3cc9d66 test3 3 pick a7e819e usera commit03 branch2 4 pick efc5b15 usera commit04 branch2 5 6 # Rebase de7b118..efc5b15 onto de7b118 (4 command(s)) 7 # 8 # Commands: 9 # p, pick = use commit 10 # r, reword = use commit, but edit the commit message 11 # e, edit = use commit, but stop for amending 12 # s, squash = use commit, but meld into previous commit 13 # f, fixup = like "squash", but discard this commit's log message 14 # x, exec = run command (the rest of the line) using shell 15 # d, drop = remove commit 16 # 17 # These lines can be re-ordered; they are executed from top to bottom. 18 # 19 # If you remove a line here THAT COMMIT WILL BE LOST. 20 # 21 # However, if you remove everything, the rebase will be aborted. 22 # 23 # Note that empty commits are commented out 这里我们可以修改pick为下面给出的其他命令,比如如果要修改提交信息,就使用r或reword,各指令的含义如下:p,pick:直接使用该次提交
r,reword:使用该次提交,但重新编辑提交信息 e,edit:停止到该次提交,通过`git commit --amend`追加提交,完毕之后不要忘记使用`git rebase --continue`完成这此rebase s,squash,压缩提交,将和上一次提交合并为一个提交 x,exec,运行命令 d,drop,移除这次提交 接下来我们看看他们如何使用。1.修改多个提交信息
使用git commit --amend在最近一次提交上追加提交,因此可以使用该命令来修改最后一次的提交信息,如果要修改做个提交信息,需要git rebase -i <commitid>打开变基脚本后在需要修改信息的提交上执行reword操作,比如以下示例:查看最近四次提交记录
$ git log --oneline -4
d0a80c2 02-b2 b6c6595 01-b2 ea2f366 b2 49afab9 commit branch3w 提交信息非常不人性化,因此对以上几个提交记录修改提交信息,将默认的pick改为r或者reword:$ git rebase -i HEAD~4
1 r 49afab9 commit branch3w
2 r ea2f366 b2 3 r b6c6595 01-b2 4 r d0a80c2 02-b2 5 # ...... 保存退出后,会弹出编译器输入提交信息,输入完毕后:$ git rebase -i HEAD~4
[detached HEAD afeaed3] commit first Date: Wed Jun 27 20:26:33 2018 +0800 1 file changed, 1 insertion(+), 1 deletion(-) [detached HEAD 528910c] commit second Date: Wed Jun 27 20:29:07 2018 +0800 1 file changed, 1 deletion(-) [detached HEAD 0e09a0f] commit 3th Date: Wed Jun 27 20:29:25 2018 +0800 1 file changed, 1 deletion(-) [detached HEAD eaed13d] commit 4th Date: Wed Jun 27 20:29:35 2018 +0800 1 file changed, 1 deletion(-) Successfully rebased and updated detached HEAD. 再次查看提交log:$ git log --oneline -4
eaed13d commit 4th 0e09a0f commit 3th 528910c commit second afeaed3 commit first 利用git rebase -i <commitid>和reword就可以完成修改多次提交信息了。2.重新排序提交
改变变基脚本中的顺序就可以对之前的提交重新排序,如: 选择最近4次提交进行处理:$ git rebase -i HEAD~4
此时会打开变基脚本,在脚本中将second这次提交放在最后一次提交中:1 pick ecd66f5 commit first
2 pick 7dbfe25 commit 3th 3 pick 82ba6a6 commit 4th 4 pick a77e06e commit second 5 # ..... 保存退出,查看提交log,发现second变为最后一次提交:$ git log --oneline -4
fe15bdb commit second 18fa9a9 commit 4th d08c408 commit 3th ecd66f5 commit first $ 3.压缩提交 如果要压缩两个提交为一次,需要git rebase -i <commitid>打开变基脚本后在需要压缩的提交上执行squash操作,当保存退出后,会将该笔提交和上一笔提交压缩为一个提交,比如:先查看当前提交记录:
$ git log --oneline -4
fe15bdb commit second 18fa9a9 commit 4th d08c408 commit 3th ecd66f5 commit first 现在我们将4th和3th这两笔提交压缩为一笔提交,在执行git rebase -i HEAD~4后在变基脚本中做如下修改:1 pick ecd66f5 commit first
2 pick d08c408 commit 3th 3 squash 18fa9a9 commit 4th 4 pick fe15bdb commit second 保存退出后,输入一下提交信息,成功后再查看log:$ git log --oneline -4
cf4159b commit second 9d73407 commit----compress 3th and 4th ecd66f5 commit first 说明合并成功了,如果要对多个提交进行合并压缩,则可以按照如下的方式:1 pick ecd66f5 commit first
2 squash d08c408 commit 3th 3 squash 18fa9a9 commit 4th 4 pick fe15bdb commit second 这表示会将first、3th、4th进行合并。4.拆分提交
如果要将一次提交拆分为多次提交,则可以将变基脚本中对应提交的指令修改为edit。拆分一个提交会撤消这个提交,然后多次地、部分地、暂存与提交直到完成你所需次数的提交。比如下面示例:首先查看提交记录:
$ git log --oneline -4
2a5c2aa commit 4th f2ceb0f commit 3th 20fe2f9 commit second c51adbe commit first 现在修改3th这次提交,将这次提交拆分为多次提交,首先执行git rebase -i HEAD~3,然后在变基脚本中将该次提交指令改为edit:1 pick 20fe2f9 commit second
2 edit f2ceb0f commit 3th 3 pick 2a5c2aa commit 4th # ...... 保存退出后,再次查看提交记录:$ git log --oneline -3
f2ceb0f commit 3th 20fe2f9 commit second c51adbe commit first 也就是说,3th这次提交是现在最近的一次提交了,我们要拆分这次提交,那就就要重置这次提交,让HEAD指针指向上一次提交:$ git reset HEAD~
现在进行多次提交:$ git add .
$ git commit -m "split commit3th---1" $ git add . $ git commit -m "split commit3th---2" ...... $ git add . $ git commit -m "split commit3th---n" 满足自己拆分后,继续完成这次rebase:$ git rebase --continue
最后查看提交记录,原来的提交被移除,新增了三条:$ git log --oneline -6
1df4a4d split 3th----3 1c22d70 split 3th----2 dbc7d91 split 3th----1 20fe2f9 commit second c51adbe commit first 5.移除提交 如果要移除某次提交,可以在变基脚本中将对应提交指令改为drop,或者直接干脆删除,比如要移除上例中新家的三个记录:1 pick c51adbe commit first
2 pick 20fe2f9 commit second 3 drop dbc7d91 split 3th----1 4 drop 1c22d70 split 3th----2 5 #pick 1df4a4d split 3th----3 查看提交记录:$ git log --oneline -6
20fe2f9 commit second c51adbe commit first rebase的风险 一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行变基操作。 因为不管是git rebase <branch>还是git rebase <commitid>,都重置了提交的SHA-1校验和,当你将本地变基后的提交推送到远端后,别人从服务器同步代码时,由于相同的内容却有不同的SHA-1校验值,因此会再此进行一次合并,于是就会有两个作者、commit信息完全相同的提交,但是SHA-1校验值不同,这无疑会带来麻烦。 因此,如果把变基当成一种在推送之前清理提交历史的手段,而且仅仅变基那些尚未公开的提交对象,就没问题。如果变基那些已经公开的提交对象,并且已经有人基于这些提交对象开展了后续开发工作的话,就会出现叫人沮丧的麻烦。