DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

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 的替代操作。