15434
相信
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
现在 在分支feature/mywork做一些修改,然后生成两个commit
$ vim README.md
$ git commit -am "xxxA"
$ vim CHANGELOG.md
$ git commit -am "xxxB"
与此同时,有些人在master分支上做了一些变更,如合并了release分支代码准备发布等。这时意味着master和feature/mywork这两个分支各自"前进"了,它们之间"分叉"了。
你可以用pull命令把master分支上的修改拉下来并且和你的修改合并;结果看起来就像一个新的"合并的提交"(merge commit):
git merge
这时feature/mywork分支历史看起来已经有分叉了,这还只是两个分支的,试想下有一个大型项目,有20个分支,同时迭代一些功能模块或者修改相同的代码块,分支树将会变成什么样?那能避免这种情况吗?答案当然是可以的,如果你想让feature/mywork分支历史看起来像没有经过任何合并一样,可以用git rebase
$ git checkout feature/mywork
$ git rebase master
先来看下效果:
解释:git rebase会把feature/mywork分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch),然后把feature/mywork分支更新到最新的master分支,最后把保存的这些补丁应用到feature/mywork分支上。
当feature/mywork分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection),这些被丢弃的提交就会删除。
现在我们可以看一下用merge和用rebase所产生的历史的区别:
解决冲突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为例:
不管是同步远程仓库代码、还是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
master分支有代码更新后,在当前分支进行了rebase操作,push后的compare
可以看出同样的commit,经过rebase操作后,SHA值发生了变化,类似上图中的C5与C5’、C6与C6’,本质上是新的commit。
可能会多次解决同一个地方的冲突(有 squash 来解决)
正常情况下feature/mywork 分支上的所有提交信息都会被合并到 master 分支上了,但这些信息对我们来说不是必要的,我们在 masetr 分支上往往只需要知道合并进来了什么新的功能即可,所以这些多余的信息可以通过git rebase的交互模式进行整合,打开变基的交互模式只需要传入一个参数 -i 即可,同时还需要指定对哪些提交进行处理,如:
$ git rebase -i HEAD~6
上述命令指定了对当前分支的最近6次提交进行操作,会看到提示:
可以使用 squash 来将所有的 commit 合并成一次提交,编辑并保存之后会出现编辑提交的信息的提示,编辑提交即可,这时候就会发现只有一次提交了,看起来十分简洁清爽。