bigywater.tech
Git · 53

revert 一个 merge commit:为什么要 -m

为什么 revert 一个 merge commit 必须加 -m?-m 1 和 -m 2 的结果有什么不同?revert 之后再合并同一个分支为什么会丢提交?这篇造出一个合并,逐项验证。

-m 1 / -m 2
撤销 feature 带来的(f1、f2 消失)与撤销 main 带来的(m2 消失)
f3 only
revert 合并之后再合并同一个 feature,只带来新增的 f3,f1、f2 没有回来
revert the revert
要先 revert 那次 revert,才能再次带入被撤销的改动

合并提交的两个 parent

实测 · 本机执行输出
  $ git log --graph --format='%s' | sed -E 's/^/  /'
    * M3 合并之后的提交
    *   合并 feature(M)
    |\
    | * F2 功能的第二步
    | * F1 功能的第一步
    * | M2 主线上的提交
    |/
    * M1 基础
  $ echo "M^1 = $(git log -1 --format=%s HEAD~1^1)"; echo "M^2 = $(git log -1 --format=%s HEAD~1^2)"
  M^1 = M2 主线上的提交
  M^2 = F2 功能的第二步
  当前文件: base.txt f1.txt f2.txt m2.txt m3.txt

合并提交 M 有两个 parent:M^1 是合并时所在的分支(这里是 main 的 M2),M^2 是被合并进来的分支的末端(feature 的 F2)。合并之后 main 上还可以继续有提交(M3)。

为什么需要 -m

实测 · 本机执行输出
  $ git revert --no-edit HEAD~1 2>&1 | head -3
  error: commit 2d8d61a21d5ec41aad93d68dc4351875e8a4b429 is a merge but no -m option was given.
  fatal: revert failed

revert 的做法是“撤销这个提交相对它的 parent 的差异”。合并提交有两个 parent,相对不同 parent 的差异完全不同,所以必须用 -m <n> 指定主线 parent,否则报 is a merge but no -m option was given。

-m 1

实测 · 本机执行输出
  文件: base.txt m2.txt m3.txt   (f1.txt、f2.txt 没了;m2.txt、m3.txt 还在)
  $ git log -1 --format=%B | head -3
  Revert "合并 feature(M)"
  
  This reverts commit 2d8d61a21d5ec41aad93d68dc4351875e8a4b429, reversing
  把现在的 HEAD 和 M 的 ^1(M2)比较,差异文件只有: m3.txt(M3 在 M 之后的改动保留了,f1、f2 的改动被撤销)

以第一个 parent(合并时所在的分支,通常是 main)为主线,撤销被合并进来的分支带来的内容。这是撤销一个已合并功能时最常用的写法;合并之后 main 上的其它提交保留。

-m 2

实测 · 本机执行输出
  文件: base.txt f1.txt f2.txt m3.txt   (m2.txt 没了,f1/f2 还在)

以第二个 parent(被合并的分支)为主线,撤销的是 main 带来的内容,结果通常不是想要的。

陷阱

实测 · 本机执行输出
  $ git merge --no-edit feature 2>&1 | head -3
  Merge made by the 'ort' strategy.
   f3.txt | 1 +
   1 file changed, 1 insertion(+)
  文件: base.txt f3.txt m2.txt m3.txt   ← 只有 f3.txt 回来了,f1.txt、f2.txt 没有回来!
  $ git log --oneline --graph | sed -E 's/^([*|\\/ ]+) [0-9a-f]{7} /\1 /'
  *   Merge branch 'feature'
  |\
  | * F3 功能的第三步(revert 之后新增)
  * | Revert "合并 feature(M)"
  * | M3 合并之后的提交
  * | 合并 feature(M)
  |\|
  | * F2 功能的第二步
  | * F1 功能的第一步
  * | M2 主线上的提交
  |/
  * M1 基础

revert 只是新增一个反向修改的提交,不改变提交图里的祖先关系。被撤销的分支提交仍是 main 的祖先,所以之后再合并同一个分支时,git 认为它们已经合并过,只带来之后新增的提交。

怎么挽回

实测 · 本机执行输出
  revert 了 revert 之后: base.txt f1.txt f2.txt m2.txt m3.txt   (f1、f2 回来了)
  $ git merge --no-edit feature 2>&1 | head -2
  Merge made by the 'ort' strategy.
   f3.txt | 1 +
  再合并 feature: base.txt f1.txt f2.txt f3.txt m2.txt m3.txt

先对那次 revert 提交再做一次 revert(把被撤销的改动恢复),之后的合并就会正常带来新增的提交。或者把 feature 分支 rebase / 重建,让它的提交成为新的提交。

分步演示

revert 合并提交
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。合并提交 M 用 --no-ff 创建;所有的 revert 使用 --no-edit;每个场景在单独的仓库里重做
    • 没有验证:revert 后用 git merge --no-ff 重新合并而不是 revert the revert 的其它办法;revert 的冲突(合并提交之后有其它改动时);git cherry-pick -m 的对应行为;章鱼合并(三个以上 parent)的 revert;多次 revert 同一个合并的历史形状
    京ICP备2026067502号-1京公网安备11010502063487号