Git撤销合并:确定哪个是父提交(-m 1 vs -m 2)

14

我正在尝试还原一个合并操作,但是我不知道是否应该使用 git revert -m 1 <merge commit's sha> 或者 git revert -m 2 <merge commit's sha>。我该如何找出哪个父提交是 -m 1,哪个是 -m 2

1个回答

27

嗯,超短的答案是始终使用-m 1。 :-) 但这需要一些解释:

  • 父提交有序,像git loggit show这样的命令会显示顺序:

    commit c13c783c9d3d7d3eff937b7bf3642d2a7fe32644
    Merge: 3f7ebc6ec 39ee4c6c2
    
    所以这里3f7ebc6ec是父级#1,39ee4c6c2是父级#2。

  • 后缀 ^ 操作采用相同的值:

    $ git rev-parse c13c783c9d3d7d3eff937b7bf3642d2a7fe32644^1
    3f7ebc6ece46f1c23480d094688b8b5f24eb345c
    
  • (当然,...^2 是另一个).

  • 绘制图表的程序(包括 git log --graph)将向您展示它们之间的连接。

  • 但更重要的是,任何合并的第一个父提交都是在合并时处于当前状态的提交。

特别地,这意味着如果您在分支 main 上运行 git merge sidebranch,则您现在(如果一切顺利)或最终(如果您必须手动解决合并)所进行的提交具有作为其第一个父提交的main 分支的上一个末尾。因此,它的第二个父提交是 sidebranch 的末尾。

那么,假设我们从这里开始:

...--o--*--A--B------C   <-- main
         \
          D--E--F--G--H   <-- sidebranch

当我们运行git merge命令时,Git会找到共同的基础提交*,然后在本质上执行以下操作来创建一个新的合并提交M:

  1. git diff * C(我们做了哪些更改?)
  2. git diff * H(他们做了哪些更改?)

然后将这两组更改合并,并将其应用于*,从而得到最终结果:

...--o--*--A--B------C--M   <-- main
         \             /
          D--E--F--G--H   <-- sidebranch

现在,如果在A-B-C中所有更改都与D-E-F-G-H中的更改完全独立无关,那么Git撤消的具体方法并不太重要,只要它保留了A-B-C的更改,同时放弃了D-E-F-G-H的更改。

但是,如果BF大部分相同,即BF都修复了一个错误,那该怎么办呢?在这种情况下,我们不想撤销BF的共享更改,因为Git采取了其中一份副本。这就是-m 1的用处。

git revert去撤消某些更改时,它会运行自己的git diff。它运行的git diff将要还原的提交与其父提交进行比较。对于任何普通的非合并提交,这很容易:比较BA,或者比较ED等,看看发生了什么,并将其撤消。但是对于合并提交,不确定要与哪个父提交进行比较(虽然它有点明显)。这里的第一个父提交是C,因此让我们看看如果运行以下命令会得到什么:

git diff C M

CM之间的变化是我们从D-E-F-G-H的更改中添加到A-B-C的已有更改中,与*相比,如果我们将它们进行比较。换句话说:

  • 如果BF完全重叠,C-vs-M中的更改是D-E-G-H:除了重叠部分以外,我们撤消了其他所有更改。

  • 如果F中的更改多于BC-vs-M中的更改是D-E-(some-of-F)-G-H:我们还原这些更改,但不包括B中的那些。

  • 如果F中的更改少于BC-vs-M中只有D-E-G-H,我们撤消这些更改。

由于第一个父级是C,我们想要撤销D-E-F-G-H的更改(不包括我们已经通过A-B-C拥有的任何更改),因此在此还原中我们要用到-m 1


这是一个非常深思熟虑的答案。谢谢! - BenjiFB
优美的回答!在您的示例中,回退时如何出现冲突? - Berthrand Eros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接