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交互,常用的命令有pickrewordsquashfixupdrop等。

pick表示保留这条commit内容和commit message。

reword表示保留commit内容,重新编辑commit message。

squash表示使用commit内容,并向前合并到保留的最近的commit上,append commit message。

fixupsquash类似,也是合并commit内容到保留的最近的commit上,不同之处在于fixup会删去此条commit message。

editreword的区别在于reword保留了commit内容,而edit允许你对commit内容做修改(通过"pausing"rebase后amend重写内容)。

这里我的期望是将21132d96f3eec8b8一个fix和对应的单元测试合并成一个并修改commit信息,15639d1081640bcb两个相同功能的log合并成一个,所以我这样编辑上面的文件,将21132d96 fixupf3eec8b8上,f3eec8b8修改commit message;将15639d10 squash81640bcb上,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

git1
2.这时候,你的同事完成了一次 hotfix,并合并入了 master 分支,此时 master 已经领先于你的 feature1 分支了:
git2
3.恰巧,我们想要同步 master 分支的改动,首先想到了 merge,执行:

git:(feature1) git merge master

git3
图中绿色的点就是我们合并之后的结果,执行:

git:(feature1) git log

就会在记录里发现一些 merge 的信息,但是我们觉得这样污染了 commit 记录,想要保持一份干净的 commit,怎么办呢?这时候,git rebase 就派上用场了。

4.让我们来试试 git rebase ,先回退到同事 hotfix 后合并 master 的步骤:
git4
5.使用 rebase 后来看看结果:

git:(feature1) git rebase master

这里补充一点:rebase 做了什么操作呢?

首先,git 会把 feature1 分支里面的每个 commit 取消掉;
其次,把上面的操作临时保存成 patch 文件,存在 .git/rebase 目录下;
然后,把 feature1 分支更新到最新的 master 分支;
最后,把上面保存的 patch 文件应用到 feature1 分支上;

git5

commit 记录我们可以看出来,feature1 分支是基于 hotfix 合并后的 master ,自然而然的成为了最领先的分支,而且没有 mergecommit 记录,是不是感觉很舒服了。

6.在 rebase 的过程中,也许会出现冲突 conflict。在这种情况,git 会停止 rebase 并会让你去解决冲突。在解决完冲突后,用 git add 命令去更新这些内容。

这里的点主要在于merge操作是在你的分支时间线上产生了一条最新的 Merge commit,而 rebase 操作则是把master分支上所有别人的更改在你切出分支之前做了一遍重放,也就是变基的含义所在了(你的分支的“基”从左边蓝色的master变到了右边蓝色的master,左边master到右边master的commit就是由“重放”产生的)。

变基的风险

呃,奇妙的变基也并非完美无缺,要用它得遵守一条准则:

如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。

如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

参考资料:

Git - 变基

彻底搞懂git rebase

标签: git

添加新评论