Git revert 合并提交报 is a merge but no -m option was given
Git 撤销合并提交时,-m 指定主线父提交编号。先核对 parent 顺序与两侧差异,再创建撤销提交,并处理冲突、回退及后续再次合并的影响。
执行 git revert HEAD 或指定某个提交撤销时,Git 拒绝继续:
error: commit <hash> is a merge but no -m option was given.
fatal: revert failed
目标是一个有多个父提交的合并提交。Git 需要知道以哪个父提交为基准计算要撤销的改动。这里的 -m 表示主线父提交编号,从 1 开始;它不是填写提交说明的选项,不能接一段 “revert merge” 文本。
先确认目标及父提交顺序,再执行带正确编号的 revert。不要看到报错就固定补 -m 1,也不要为了试编号反复硬重置工作区。
先确认要撤销的是哪次合并
本文适用于普通 Git 工作仓库,目标是生成一个新提交,抵消某次合并带来的文件改动。它不会把分支直接移动回旧提交,也不会从历史中删除合并记录。如果你想“恢复到某个旧版本的全部文件”,那与撤销一个提交的改动并不等价。
开始前保存未提交工作和需要保留的未跟踪文件,确认操作所在分支,并记录当前提交 ID。本文按工作区与暂存区干净、没有其他 merge、rebase 或 revert 正在进行的情况处理:
git status
git show --no-patch --format=raw HEAD
下面的 MERGE_COMMIT 是占位值,必须替换为实际合并提交的完整 ID。即使出错时使用的是 HEAD,也应先记录这个固定 ID,避免后来 HEAD 移动后查看或撤销了另一个提交。
git show --no-patch --format=raw MERGE_COMMIT
查看输出中的 parent 行。两个父提交就是两行,第一行对应 -m 1,第二行对应 -m 2。编号来自提交对象中的顺序,不取决于今天哪个分支叫 main,也不取决于图形工具把哪条线画在左侧。多于两个父提交的合并需要逐个确认,不能直接套用下面的双亲示例。
若输出只有一个父提交,应重新核对报错里的 ID 与当前查看对象,别继续猜编号。
-m 选择保留的比较基准,不是要删除的分支
假设当时在主分支上合入功能分支,结构如下。字母仅表示示意提交:
A---B---M
\ /
C---D
通常 M 的第一个父提交是合并前所在分支的 B,第二个父提交是被合入的 D。要撤销这次合并相对 B 引入的改动,就以 B 为基准,选择 -m 1。实际仍要以 parent 行及提交内容为准。
分别比较合并提交与两个父提交:
git diff 'MERGE_COMMIT^1' MERGE_COMMIT
git diff 'MERGE_COMMIT^2' MERGE_COMMIT
^1、^2 表示该提交的第一、第二父提交;它们不是向前数一、两个提交。引号用于保护带特殊字符的提交表达式,替换 ID 时保留引号即可。
第一条差异描述从第一个父提交到合并结果发生了什么。使用 -m 1 时,revert 尝试反向应用这部分改动。选择第二个父提交会得到另一组差异,因此不能用“1 不成功就试 2”来决定主线。命令成功与撤销方向正确是两件事。
合并时手工解决冲突的结果也属于合并提交的内容。即使某个文件不是功能分支单独修改的,也可能出现在相对父提交的差异中,应在执行前看清楚。
确认主线后执行一次 revert
只有确认第一父提交是需要保留的基准时,才执行这个示例:
git revert -m 1 MERGE_COMMIT
如果需要的是第二父提交,就把编号改成 2,不要更换成分支名。默认流程会生成撤销提交,并可能打开编辑器填写说明;可以在说明中补充撤销原因。
合并之后如果已有其他提交,Git 会在当前分支状态上应用反向改动,不会顺带丢弃所有后续历史。不过后续修改可能与待撤销内容重叠,因此仍可能冲突,也可能需要额外业务修正。撤销一个合并不是把整个目录恢复成当时某个父提交的快照。
出现冲突后,先用 git status 确认当前确实处于这次 revert。逐个处理文件并暂存解决结果。下面的路径应替换为实际处理好的文件:
git add -- path/to/resolved-file
git diff --cached --check
git diff --cached
git revert --continue
检查暂存差异,确认没有夹带无关改动,再继续。--check 能发现差异中的冲突标记和部分空白错误,不能证明逻辑正确;项目相关测试仍需通过。
如果发现目标或主线选错,且 revert 仍处于进行中,先另存这期间值得保留的手工修改,再取消:
git revert --abort
abort 返回本次序列开始前的状态;它适用于尚未结束的操作。只收到缺少 -m 的报错、没有进入进行中状态时,应先确认状态,不必盲目执行 abort。撤销提交已经生成以后,也不能用 abort 删除这个已完成提交。
验收要看新提交的差异和业务结果
操作完成后检查:
git status
git show --stat HEAD
git show HEAD
确认新增的是预期的撤销提交,改动对应此前选定父提交到合并结果的反向变化。若当前分支在合并后还有其他改动,解决冲突后的差异需要结合这些改动判断,不能要求机械地逐行相反。
随后运行项目已有的相关检查,重点确认要撤掉的功能不再生效,保留的功能及后续修复仍正常。工作区干净只说明没有未提交修改,并不代表功能已恢复。涉及应用部署的项目,还要通过原来的发布流程交付新版本;Git 仓库生成提交不会自动撤销线上配置或数据库变更。
如果生成的普通撤销提交已进入共享历史,后来发现需要恢复它的改动,可在确认其内容和后续影响后,再撤销这个普通提交。这样会增加一条记录;不要把对合并提交使用的主线编号照搬过去。恢复时同样可能冲突,不能保证无条件还原业务状态。
撤销合并会影响下一次合并的结果
revert 抵消文件改动,但原合并提交及其祖先关系仍在历史中。因此,以后再合并同一条开发线时,Git 不会自动把已经包含在那次合并历史里的旧改动重新带回来;后续合并主要引入未成为那次合并祖先的新提交所带来的变化。
临时撤下功能、修好后再合入时,也要考虑这一点。修复前应先决定是恢复撤销的改动、再合入修复,还是重做一套可独立合入的提交,并检查依赖关系。单纯再执行一次 merge,不能当作撤销 revert 的替代操作。


