Git Rebase 两个常用功能
个人觉得对于Git这个工具,能说自己会用必须要基本掌握git rebase这条命令。这个命令也是非常强大的,下面就讲述一下两个开发中非常常用的场景:
- 多人开发中避免不必要的
merge操作 - 修改自己的提交历史
由于各种Git GUI工具五花八门,所讲述的功能不一定所有的GUI都支持,所以这里就以CLI(命令行接口)为例子来讲述,理解了命令行图形界面就简单很多。
避免不必要的merge
这是开发中最常遇到的问题,大概的GitFlow图形如下

最后合并阶段就是很头疼的问题,dev分支上充满了无意义(个人认为)的merge提交,而且大多数人不是很在意出现冲突后的合并。有些时候你会发现你明明删除掉的代码在merge之后它又回来了(这是真实发生过的事情)
其实这个问题可以使用git rebase解决。
使用rebase拯救臃肿的dev分支
可以看出,dev分支臃肿的原因是充满了merge提交,要如何消除这些merge呢?我们分几种情况讨论:
本地当前分支已经merge到dev中,且已经推送到了远端
抱歉,这种情况除了改写远端别无他法。而多人开发中这意味着如果你强制覆盖了远端,即使用了--force选项,那么其他人就无法顺利地将自己的修改提交到远端,会引发更大的问题,所以这种情况下请等待下次吧。
本地当前分支已经merge到dev中,但尚未推送到远端
大概图例如下:

这是最后的拯救机会,首先要移除本地的这个merge提交,但注意要使用混合模式,即git reset的默认模式,这种会将你的修改保留,但移除掉对分支树的修改,一般只需执行
git reset HEAD~1这个命令的意思是,撤销HEAD指向的提交(即本地的最新的提交),执行成功后大概图例如下:

这就到了最简单的情况——
本地当前分支尚未merge到dev中
这里我们要做的事情就很简单了,即把上面一张图中的分支树变成下图这样

这里就完美说明了rebase这个词的含义,即将3这个提交的上一级提交(即base),例子中是1,更换为2这个提交。它的命令也比较简单:
git rebase dev它代表了会把当前分支(即git status显示的分支)的内容rebase到dev分支上,这一步分为两种情况,一种无需解决冲突,即如果你的提交和dev分支上没有冲突的话,那就可以很轻松的完成。另一种需要解决冲突,点击 跳转至解决冲突 一节。
无需解决冲突
# rebase前
$ git log --all --oneline --graph
* 6355958 (dev) add other commit
| * 4717a58 (HEAD -> feature/new) add new feature, modify 1.txt
|/
* ab0643c first commit, create 1.txt
$ git rebase dev
Successfully rebased and updated refs/heads/feature/new.
# rebase后
$ git log --all --oneline --graph
* aa98dad (HEAD -> feature/new) add new feature, modify 1.txt
* 6355958 (dev) add other commit
* ab0643c first commit, create 1.txt它的好处我们在上图也可以看出来,完全没有merge提交,提交历史清晰,向远端提交也没有任何压力,直接git push origin HEAD:dev即可,因为前面的内容完全一致,远端只需执行Fast-Forward合并即可。
同时也可以看出,这个rebase命令是会删除老的在feature\new分支上的SHA为4717a58的提交,再在dev分支上SHA为6355958这次提交后追加一个新的aa98dad的新提交的,虽然内容完全一样,但它已经是一个新提交了。
需要解决冲突
如果Git不能决定如何快进,就需要自己手动解决冲突。这和merge时遇到的冲突需要合并是一样的。
假设rebase之前代码是这样的,分为feature/new分支和dev分支
# rebase前
$ git log --all --oneline --graph
* 6e03fd3 (HEAD -> feature/new, origin/feature/new) change to 2
| * fa4959a (origin/dev, origin/HEAD, dev) modify code.txt
|/
* 76e43f7 add code.txt但使用git rebase时并不能直接完成,而是提示冲突。
$ git rebase dev
Auto-merging code.txt
CONFLICT (content): Merge conflict in code.txt
error: could not apply 6e03fd3... change to 2
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config advice.mergeConflict false"
Could not apply 6e03fd3... change to 2这时候打开code.txt,就会发现他的内容变成了
first commit
<<<<<<< HEAD
this content will be 1
// no comment
=======
this content will be 2
>>>>>>> 6e03fd3 (change to 2)我们想调整的是把1变为2,然后保留// no comment这一行,则直接编辑变成这样,保存。所有有冲突的文件都会被处理成这样的格式,我们需要一一修改,改漏了也不用担心,git status随时可以查看哪些文件尚未解决冲突。
first commit
this content will be 2
// no comment然后在命令行git add修改的文件。可以使用git add code.txt添加完成了手动合并操作的文件,或者使用git add .添加所有修改后的冲突文件。
然后执行git rebase --continue,会添加一个解决冲突的提交,写上你的解决提交内容,我个人是强烈建议写上rebase字样,以便以后查找修改,撰写完毕保存即可。再看一下提交日志:
$ git log --oneline --all --graph
* 781e3cc (HEAD -> feature/new) change to 2
* fa4959a (origin/dev, origin/HEAD, dev) modify code.txt
| * 6e03fd3 (origin/feature/new) change to 2
|/
* 76e43f7 add code.txt可以看出新增了781e3cc这个提交,成功地将6e03fd3重新变换了 base 提交。
接下来同样地,将当前的分支(feature/new或者说HEAD)提交到远端dev即可。
$ git push origin HEAD:dev
# 再次查看提交历史
$ git log --oneline --all --graph
* 781e3cc (HEAD -> feature/new, origin/dev, origin/HEAD) change to 2
* fa4959a (dev) modify code.txt
| * 6e03fd3 (origin/feature/new) change to 2
|/
* 76e43f7 add code.txt至于这个多余的origin/feature/new在确认合并没有问题之后可以删除。
$ git push origin --delete feature/new至此,通过rebase重排提交,且出现冲突的例子解释完毕。但有些特殊情况。
但有些开发环境的合并分支并不能直接使用
merge,而是使用Pull Request,这种情况就不适用这个方案,因为这种情况下dev分支极有可能是不允许直接merge的,所以最后的git push origin HEAD:dev并不能执行成功
Git的改变
你可能会好奇我根本没有做merge,它这个merge提交是怎么生成的?
答案就在这个git pull上面,一般我们在准备要合并自己的分支到dev时,会习惯性的执行git pull,这个命令相当于两个命令的结合git fetch和git merge(仅限老版本的Git,默认pull是基于merge,新版本的Git安装式会询问pull是基于rebase还是merge),它会拉取远端分支上的内容,将其merge到本地分支中,所以这次merge提交是由pull请求生成的。
但新版的Git已经意识到这个问题,开始将pull的合并策略改为可调整的,如果用rebase的话就可以减少很多麻烦的工作。