git rebase,看这一篇就够了

42,250 阅读2分钟

相信git merge大家都不陌生,平时开发中少不了创建Merge Request,但git rebase估计就用的很少了。自从去年开发过程中接近20个分支同时迭代并且有大量开发并提交commit时,偶然间接触到git rebase,索性就研究了下,之后一直使用git rebase,真香~~~,先放上建议,才能明白为什么要说git rebase:

  • git merge:当需要保留详细的合并信息的时候建议使用,特别是需要将分支合并进入master分支时
  • git rebase:当发现自己修改某个功能时,频繁进行了git commit提交时,发现其实过多的提交信息没有必要时使用,分支多,内容多时也可以考虑使用

假设现在有基于远程分支“origin/master”,更新至本地最新“master”,创建一个叫“feature/mywork”的分支进行说明

git rebase

$ git checkout -b feature/mywork

1.jpg

现在 在分支feature/mywork做一些修改,然后生成两个commit

$ vim README.md
$ git commit -am "xxxA"

$ vim CHANGELOG.md
$ git commit -am "xxxB"
...

与此同时,有些人在master分支上做了一些变更,如合并了release分支代码准备发布等。这时意味着masterfeature/mywork这两个分支各自"前进"了,它们之间"分叉"了。 2.jpg

你可以用pull命令把master分支上的修改拉下来并且和你的修改合并;结果看起来就像一个新的"合并的提交"(merge commit):

git merge

3.jpg

这时feature/mywork分支历史看起来已经有分叉了,这还只是两个分支的,试想下有一个大型项目,有20个分支,同时迭代一些功能模块或者修改相同的代码块,分支树将会变成什么样?那能避免这种情况吗?答案当然是可以的,如果你想让feature/mywork分支历史看起来像没有经过任何合并一样,可以用git rebase

$ git checkout feature/mywork
$ git rebase master

先来看下效果:

4.jpg

解释:git rebase会把feature/mywork分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch),然后把feature/mywork分支更新到最新的master分支,最后把保存的这些补丁应用到feature/mywork分支上。

feature/mywork分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection),这些被丢弃的提交就会删除。

5.jpg

现在我们可以看一下用merge和用rebase所产生的历史的区别:

6.jpg

7.jpg

解决冲突CONFLICT

在rebase的过程中,也许会出现冲突(conflict)。在这种情况,Git会停止rebase并会让你去解决冲突;在解决完冲突后,用git add命令去更新这些内容的索引(index),然后,你无需执行 git commit,只要执行:

$ git rebase --continue

这样git会继续应用(apply)余下的补丁。

在任何时候,你可以终止rebase的行动,并且feature/mywork分支会回到rebase开始前的状态。

$ git rebase --abort

在命令行使用git rebase存在多个commit、多个冲突时需要我们多次解决同一个地方的冲突,然后执行git rebase --continue,反复,直到冲突解决为止,稍显麻烦,可以使用IDE辅助进行,如JetBrains家族的IDE系列对VCS都有很好的支持,最新版的更是直接将VCS变为Git,以IntelliJ IDEA为例:

8.png

不管是同步远程仓库代码、还是Merge/Rebase、或是push都有很好的支持,再配合Git Commit Template插件,可以完美的coding。

git rebase解决冲突后,无法push怎么办?

有过git rebase经验的同学都知道,多人协作并行开发时刚解决完一堆冲突后,松了一口气,push时又提示拒绝,什么情况???然后一查,用-f或者--force参数强制推送,发现就推送成功了,但很多人可能忽略了一个问题: git push --force 是不安全的

  • 生产过程中碰到过一次,rebase后强制push,同一分支的其他同学pull代码时出错,强行覆盖也不行。所以除非有充分的强制推送理由,其他情况下,不建议使用git push --force
  • 这里将推荐 --force-with-lease 参数,让我们可以更安全地进行强制推送,Git 的 1.8.5 版本开始提供了这个参数,旨在解决 git push --force 命令造成的安全问题。如果你对这样的危险没有什么直观的感觉,可以看看这则新闻:还在用 Git 的 -f 参数强推仓库,你这是在作死!

关于git push --force-with-lease更加详细的可以自己查查,这里推荐一篇:Git 更安全的强制推送,--force-with-lease

理解后多使用,自己才能深刻体会为什么--force-with-lease--force更加安全。

总结

git merge 一致,git rebase 的目的也是将一个分支的更改并入到另外一个分支中去。如一中图所示主要特点如下:

  • 改变当前分支从 master 上拉出分支的位置

  • 没有多余的合并历史的记录,且合并后的 commit 顺序不一定按照 commit 的提交时间排列,同一个commit的SHA值会发生变化,如下图:

    • 未合并master分支前push后的compare

      9.png

    • master分支有代码更新后,在当前分支进行了rebase操作,push后的compare

      10.png

      可以看出同样的commit,经过rebase操作后,SHA值发生了变化,类似上图中的C5与C5’C6与C6’,本质上是新的commit。

  • 可能会多次解决同一个地方的冲突(有 squash 来解决) 正常情况下feature/mywork 分支上的所有提交信息都会被合并到 master 分支上了,但这些信息对我们来说不是必要的,我们在 masetr 分支上往往只需要知道合并进来了什么新的功能即可,所以这些多余的信息可以通过git rebase的交互模式进行整合,打开变基的交互模式只需要传入一个参数 -i 即可,同时还需要指定对哪些提交进行处理,如:

$ git rebase -i HEAD~6

上述命令指定了对当前分支的最近6次提交进行操作,会看到提示:

#
# 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.
#

可以使用 squash 来将所有的 commit 合并成一次提交,编辑并保存之后会出现编辑提交的信息的提示,编辑提交即可,这时候就会发现只有一次提交了,看起来十分简洁清爽。