Fork me on GitHub

git revert踩坑之路

背景

在实际的开发工作中,使用git总会遇到一堆问题,本文将结合具体例子,讲述在何种条件下会出发git revert失败以及解决的方案和措施

准备工作

首先,创建一个git 仓库(repo),本人是在Mac环境下,Windows下打开git 命令行,但代码是一样的:

1
2
3
mkdir git-revert
cd git-revert
git init

首先创建两个 commit 来模拟 master 上现有的 commit 记录:

1
2
3
4
echo 'file1' > file1 
git add . && git commit -m 'commit 1'
echo 'file2' > file2
git add . && git commit -m 'commit 2'

现在我们需要开发一个新功能,所以需要基于master分支拉了一个新分支dev,我们创建并切换到dev分支:

1
2
3
git branch dev
git checkout dev
# 或者使用 git checkout -b dev 合上面两步效果一样

接下来我们添加两个commit来完成dev分支:

1
2
3
4
echo 'file3' > file3
git add . && git commit -m 'dev - commit 1'
echo 'file4' > file4
git add . && git commit -m 'dev - commit 2'

在dev分支开发过程中,master 分支上通常会有其他人新的 commit提交,于是我们回到 master,来模拟一下这些 commit:

1
2
3
4
5
git checkout master
echo 'file5' > file5
git add . && git commit -m 'commit 3'
echo 'file6' > file6
git add . && git commit -m 'commit 4'

这个时候,dev分支测试通过了,需要合并到master分支上:

1
git merge dev

如图所示:
在这里插入图片描述
后来,master 上又多了一些 commit:

1
2
echo 'file7' > file7
git add . && git commit -m 'commit 5'

由于dev分支有问题,不想合并master分支,想revert这次merge commit,那就revert吧。
首先,先git log看一下:
在这里插入图片描述
找到merge的commit id来revert,711b06,
在这里插入图片描述

1
git revert 711b06

问题出现了,revert失败:
在这里插入图片描述

分析问题

再次给出错误信息:

1
error: commit 711b06365ec6ced517bf2597fa1b7562060e1181 is a merge but no -m option was given.

我们来看看 -m 到底指的是什么, 查看官方文档, 可以看到:

1
2
3
4
5
-m parent-number
--mainline parent-number
Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.

Reverting a merge commit declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge. This may or may not be what you want.

翻译过来就是:

1
2
3
通常情况下,你无法 revert 一个 merge,因为你不知道 merge 的哪一条线应该被视为主线。这个选项(-m)指定了主线的 parent 的代号(从1开始),并允许以相对于指定的 parent 的进行 revert。

revert 一个 merge commit 意味着你将完全不想要来自 merge commit 带来的 tree change。 因此,之后的 merge 只会引入那些不是之前被 revert 的那个 merge 的祖先引入的 tree change,这可能是也可能不是你想要的。

由于 merge commit 是将两条线合并到一条线上,因此,合并时的那个commit,将具有两个祖先。所以 git 不知道 base 是选择哪个 parent 进行 diff,所以你要用 -m 属性显示地告诉 git 用哪一个 parent。
那么,如何查看当前的commit有几个祖先呢?

1
git show 711b06

在这里插入图片描述
Merge 这个字段便标明了当前的parent,分别是 0ffc72f 和 8f1dbff

当你在 B 分支上把 A merge 到 B 中,那么 B 就是merge commit 的 parent1,而 A 是 parent2,所以,master分支是parent1,dev分支是parent2。

解决

有了上一节的分析,我们可以很直接地给出以下可用的代码:

1
git revert 711b06 -m 1

输出以下log:

1
2
3
4
5
6
7
8
9
10
11
12
Revert "Merge branch 'dev'"

This reverts commit 711b06365ec6ced517bf2597fa1b7562060e1181, reversing
changes made to 0ffc72ff1095bb8c70e72f39ab99e8102ce480b9.

Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit.

On branch master
Changes to be committed:
deleted: file3
deleted: file4

:wq 退出看到:
在这里插入图片描述
file3 和 file4 是dev上的 commit 引入的文件,被正确地删掉了。

结论

  • 对于单一 parent 的 commit,直接使用 git revert commit_id;
  • 对于具有多个 parent 的 commit,需要结合 -m 属性:git revert commit_id -m parent_id;
  • 对于从 branch 合并到 master 的 merge commit,master 的 parent_id 是1,branch 的 parent_id 是2, 反之亦然;

参考以下文章
当你决定去 revert 一个merge commit