什么是git revert
在正式开始之前,我们先简单介绍下revert命令:revert允许我们“回滚”之前的一次或多次提交。
当我们执行git revert <commit_id>
时,git会创建一个新的commit,在这个新commit中,git通过打一个反向补丁的方式来撤销<commit_id>这次提交中的内容。
相比于使用reset命令撤销提交,revert会创建一个新的commit,这使得git的提交线更加清晰,因此在多人协作中我更推荐使用revert命令。
不过上面介绍的revert用法有一个前提,就是<commit_id>不能是一个合并提交(合并提交是指合并两个分支时产生的那个提交)。那如果我们真的想revert一个合并提交又该怎么做呢?
背景介绍
为了更好地说明,首先我们使用git init
创建一个空的git仓库,并进行一次初始化提交:
接着使用git checkout -b dev
创建一个dev分支,然后马上切换回默认的master分支。
在默认的master分支下,我们分别进行了两次提交:第一次创建了master.txt文件并在其中写下“master分支第一次提交”,第二次在master.txt文件中再写下“master分支第二次提交”。
接着再切换到dev分支,还是进行两次提交:第一次创建dev.txt并写下“dev分支第一次提交”,第二次在dev.txt文件中再写下“dev分支第二次提交”
然后切换回master分支,并合并dev分支。
现在,我们的git实验仓库里应该有master.txt和dev.txt两个文件,内容分别为:
master.txt:
master分支第一次提交
master分支第二次提交
dev.txt:
dev分支第一次提交
dev分支第二次提交
git的提交线看上去是这样:
假设这时我们想撤销这次合并,如果直接使用git revert 67a7b26e
(67a7b26e是合并master和dev分支那次提交的id前几位)会得到下面的错误提示:
error: commit 67a7b26e40962a8fe6e2ff087f6c39217d7ceb5d is a merge but no -m option was given.
fatal: revert failed
问题分析
在不考虑冲突的情况下,合并提交的内容来源于多个被合并的分支。
当我们想要revert一个合并提交时,git并不能确定我们想要保留哪个分支的内容,所以就会报错并阻止revert操作。
解决方法
此时需要使用-m(或–mainline,二者是等价的)选项指定我们想保留哪个分支。
-m参数后面跟一个从1开始的数字,代表哪个分支是我们想保留的,除该分支的内容外,其他被合并分支上的内容会被撤销。
那么,如何知道每个分支对应的数字是什么呢?
我们可以使用show命令来查看,比如这里是git show 67a7b26e
,它的输出中会包括下面的内容:
Merge: f3480f7 2ef1836
其中f3480f7是master分支上一次提交,2ef1836是dev分支上一次提交,所以这种情况下master对应1,dev对应2。
如果我们想保留master分支上的内容,撤销合并进来的dev分支,可以使用git revert -m 1 67a7b26e
。
执行后可以发现合并进来的dev分支上的修改已经全部消失了,此时的提交线是这样的:
注意事项
从提交线可以看出,当我们revert一个合并提交时,只是回滚了内容,但分支合并的记录还是存在的。
那么这会有什么影响呢?
假设我们又在dev分支上进行了提交:创建一个dev1.txt并写下“这是dev分支在revert -m之后的修改”,然后切换到master分支并执行git merge dev
。
我们会发现此时git仓库中只有dev1.txt,也就是dev分支在上一次合并之后的修改才被合并到了master分支中。
其实这也可以理解,虽然我们revert了上一次合并,但因为合并记录还在,所以对于git而言,dev分支的前两次提交已经被合并到了master分支上,那么本次执行merge就只会合并dev分支上增量部分。
如果我们还是想保留dev分支上的前两次提交,可以再对使用git revert -m 1 67a7b26e
产生的提交进行一次revert操作。