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
的话就可以减少很多麻烦的工作。