Git Rebase 学习
git rebase:对一个分支执行变基操作。常用场景:commit压缩和并行分支合并
使用 rebase 压缩 commit
在日常开发过程中,经常无法保证commit的原子性,产生了多条相同功能点或含义的commit,这种情况下在进行code review时就会带来很多麻烦,且后续如果要回滚到某个时间点也会产生困扰,影响开发效率和部署的健壮性。此时可以使用git rebase
命令来对多条连续的commit进行压缩操作,保证push到远程分支上的代码保持条理性和清晰性。
以一次实际开发过程为例,使用git rebase -i HEAD~5
命令对最近5条commit做修改,注意这里列出的commit时间顺序和git log列出的时间顺序是相反的(git log
是最上面最新,rebase
是最下面最新):
pick f3eec8b8 test: 添加完善合并请求!181单元测试
pick 21132d96 fix: comment
pick 81640bcb log: optimize log
pick 15639d10 log: add custom log
pick 0d3615e4 fix: 一次发布多个集群成功率统计
# Rebase 9d0c4c30..0d3615e4 onto 15639d10 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# 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.
#
-i
参数表示使用editor交互,常用的命令有pick
、reword
、squash
、fixup
、drop
等。
pick
表示保留这条commit内容和commit message。
reword
表示保留commit内容,重新编辑commit message。
squash
表示使用commit内容,并向前合并到保留的最近的commit上,append commit message。
fixup
和squash
类似,也是合并commit内容到保留的最近的commit上,不同之处在于fixup
会删去此条commit message。
edit
和reword
的区别在于reword
保留了commit内容,而edit
允许你对commit内容做修改(通过"pausing"rebase后amend重写内容)。
这里我的期望是将21132d96
、f3eec8b8
一个fix和对应的单元测试合并成一个并修改commit信息,15639d10
、81640bcb
两个相同功能的log合并成一个,所以我这样编辑上面的文件,将21132d96
fixup
到f3eec8b8
上,f3eec8b8
修改commit message;将15639d10
squash
到81640bcb
上,81640bcb
使用pick
保留:
reword f3eec8b8 test: 发布成功率统计单元测试
fixup 21132d96 fix: comment
pick 81640bcb log: optimize log
fixup 15639d10 log: add custom log
pick 0d3615e4 fix: 一次发布多个集群成功率统计
After:
commit 42b810e5c5fe5f35442927f3bf69aea92d250f76 (HEAD -> optimize)
Author: 孙永飞_sunyongfei <sunyongfei@enmonster.com>
Date: Mon Oct 12 16:42:32 2020 +0800
fix: 一次发布多个集群成功率统计
commit 9f2ac0c990dc00d2cea92f04035fdb934b1f7ebd
Author: 孙永飞_sunyongfei <sunyongfei@enmonster.com>
Date: Wed Sep 30 16:00:08 2020 +0800
log: optimize log
commit ab1fb754a97d75d3e195b56f1c41e7c477577dc5
Author: 孙永飞_sunyongfei <sunyongfei@enmonster.com>
Date: Tue Sep 29 15:47:32 2020 +0800
test: 发布成功率统计单元测试
再例如我想删除时间线中间的一个commit:
pick f3eec8b8 test: 发布成功率统计单元测试
drop 81640bcb log: optimize log
pick 0d3615e4 fix: 一次发布多个集群成功率统计
After:
enmonster@MI-LAPTOP-SYF /d/w/pub-ms (optimize)> git log
commit 6120322aad07962b8487263128c011ac9b06aaf3 (HEAD -> optimize)
Author: 孙永飞_sunyongfei <sunyongfei@enmonster.com>
Date: Mon Oct 12 16:42:32 2020 +0800
fix: 一次发布多个集群成功率统计
commit ab1fb754a97d75d3e195b56f1c41e7c477577dc5
Author: 孙永飞_sunyongfei <sunyongfei@enmonster.com>
Date: Tue Sep 29 15:47:32 2020 +0800
test: 发布成功率统计单元测试
可以看到5条commit已经压缩成了3条,提交记录比原先清晰许多。
另外需要注意的是不论是reword
还是pick
保留的commit,其commit编号都和之前不同了,相当于删除了之前的commit并产生了一个新的commit,这一点需要注意,最好在push到远程分支之前先检查本地的提交记录,保证rebase
操作仅操作本地的记录后再push,否则需要加上-f
参数强制push到远程分支,这种方式是及其不推荐的,因为这样会覆盖远程分支的commit记录,会对别人产生困扰。
如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
使用 rebase 合并分支
1.我们先从 master
分支切出一个 dev
分支,进行开发:
git:(master) git checkout -b feature1
2.这时候,你的同事完成了一次 hotfix
,并合并入了 master
分支,此时 master
已经领先于你的 feature1
分支了:
3.恰巧,我们想要同步 master
分支的改动,首先想到了 merge
,执行:
git:(feature1) git merge master
图中绿色的点就是我们合并之后的结果,执行:
git:(feature1) git log
就会在记录里发现一些 merge
的信息,但是我们觉得这样污染了 commit
记录,想要保持一份干净的 commit
,怎么办呢?这时候,git rebase
就派上用场了。
4.让我们来试试 git rebase
,先回退到同事 hotfix
后合并 master
的步骤:
5.使用 rebase
后来看看结果:
git:(feature1) git rebase master
这里补充一点:rebase
做了什么操作呢?
首先,git
会把 feature1
分支里面的每个 commit
取消掉;
其次,把上面的操作临时保存成 patch
文件,存在 .git/rebase
目录下;
然后,把 feature1
分支更新到最新的 master
分支;
最后,把上面保存的 patch
文件应用到 feature1
分支上;
从 commit
记录我们可以看出来,feature1
分支是基于 hotfix
合并后的 master
,自然而然的成为了最领先的分支,而且没有 merge
的 commit
记录,是不是感觉很舒服了。
6.在 rebase
的过程中,也许会出现冲突 conflict
。在这种情况,git
会停止 rebase
并会让你去解决冲突。在解决完冲突后,用 git add
命令去更新这些内容。
这里的点主要在于merge操作是在你的分支时间线上产生了一条最新的 Merge commit,而 rebase 操作则是把master分支上所有别人的更改在你切出分支之前做了一遍重放,也就是变基的含义所在了(你的分支的“基”从左边蓝色的master变到了右边蓝色的master,左边master到右边master的commit就是由“重放”产生的)。
变基的风险
呃,奇妙的变基也并非完美无缺,要用它得遵守一条准则:
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase
命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
参考资料: