skip to content

Why does git revert require the -m option when reverting a merge commit?

level: seniorimportance: must knowfreq 45%

answer

  1. the commit has two parents
  2. which diff did you mean?
  3. the flag takes a number, not a sha
  4. first parent is the branch you were on
  5. content is undone, ancestry is not

basics

~20 s

A merge commit has two parents, so "the change it introduced" is ambiguous — it differs depending on which parent you compare against. -m <parent-number> names the mainline parent to treat as the branch you kept, and Git undoes the change relative to that one.

solid answer

~50 s

Reverting means applying the inverse of the change a commit introduced, and that change is defined as the diff against its parent. A merge commit has **two** parents, so there are two possible diffs and Git refuses to guess: it errors with a message about a mainline not being specified. `git revert -m 1 <merge>` says "treat parent 1 as the mainline", so the revert undoes everything the *other* side brought in, leaving the mainline's history effect intact. Parent 1 is the branch you were on when you merged — usually the main branch — so `-m 1` is the common case; `-m 2` would undo the mainline's changes instead, which is almost never what you want. Check with `git log --format=%P <merge>`, which lists the parents in order. The result is an ordinary commit, and the important consequence is that ancestry is untouched — only content is undone.

code

console · 7 lines
console
$ git revert 7c31af0
error: commit 7c31af0 is a merge but no -m option was given.
fatal: revert failed
$ git log --format=%P -1 7c31af0
2f81b6c 9f2a1c3
$ git revert -m 1 7c31af0
[main 51ad7b2] Revert "Merge branch 'feature/rate-limit'"

go deeper

for a junior

Know that a merge commit has two parents and that reverting one requires telling Git which parent counts as the mainline.

for a middle

Explain why the change a merge introduced is only defined relative to a chosen parent, and how to inspect parent order before picking a number.

for a senior

Show the operational judgment: verify the parent, consider reverting the individual commits instead, and flag that ancestry still says the branch is merged.

for a principal

Own the rollback policy for integrated work — when a whole merge is withdrawn versus fixed forward, and how the team handles branches whose merges were reverted.

## Why the ambiguity exists `git revert` applies the inverse of "the change this commit introduced". For an ordinary commit that is unambiguous: diff it against its single parent. A merge commit has two parents. Diffed against the first parent, the merge introduced everything the side branch added. Diffed against the second, it introduced everything the mainline added while the side branch was in progress. Those are different — often wildly different — changes, and Git will not pick one for you. Running `git revert` on a merge without `-m` fails immediately with an error saying the commit is a merge but no mainline was given. ## What -m selects `-m <parent-number>` is 1-based and indexes the merge's parent list: - `-m 1` — treat the **first** parent as the mainline. The first parent is the branch you had checked out when you ran `git merge`, so on a normal `main` merging `feature` it is `main`. The revert then removes what `feature` brought in. - `-m 2` — treat the second parent as the mainline, undoing what the first side contributed. Occasionally right, usually a mistake. Inspect before choosing: `git log --format=%P -1 <merge>` prints both parent ids in order, and `git show <merge>` names them too. `<merge>^1` and `<merge>^2` address them directly, so `git diff <merge>^1 <merge>` shows exactly the change `-m 1` will invert. ## What the resulting commit is An ordinary single-parent commit whose diff removes the merged-in changes, with a generated message like `Revert "Merge branch 'feature'"` and the mainline noted. It can conflict, in which case Git records `REVERT_HEAD` and you resolve, `git add`, and `git revert --continue`; `--abort` restores. ## The consequence people miss A revert changes **content**, never **ancestry**. The merge commit is still in history, so every commit from the reverted branch is still an ancestor of the branch — Git still considers them merged, even though their changes are no longer in the tree. That is exactly the setup for the notorious problem that appears the next time somebody tries to merge that branch again, and it is why reverting a merge is treated as a decision rather than a routine undo. ## Alternatives worth weighing Before reverting a merge, consider whether you want something else: - **Revert the individual commits** the merge brought in, rather than the merge itself. More commits, but it keeps the ancestry story simpler, and re-landing them later is less surprising. - **If the merge is not yet published**, undoing it locally by moving the branch back is simpler and leaves no residue at all. - **Fix forward** — often the merge brought in ten good changes and one bad one, and reverting the whole merge is a blunt instrument that also removes the nine. The reason `-m` is such a common interview question is that it forces you to say out loud what a merge commit *is*: a commit with two parents, whose "change" only means something relative to a chosen side. Candidates who have only ever run `git revert <sha>` on ordinary commits usually stall on it. ## Checklist for doing it safely 1. Confirm the commit really is a merge: `git show --format=%P -s <sha>` prints two ids. 2. Decide which side you are keeping and confirm the parent order rather than assuming. 3. Run `git revert -m 1 <merge>` (or 2, deliberately). 4. Verify the tree: diff against the commit before the merge to confirm only the intended side's changes disappeared. 5. Write down — in the commit message or the tracker — that this branch will need special handling if it is ever merged again, because ancestry still says it is merged.

  • How do you check which parent is number 1 before choosing -m?
    Ask Git rather than assume: `git log --format=%P -1 <merge>` prints the parent ids in order, and `<merge>^1` / `<merge>^2` address them directly. `git diff <merge>^1 <merge>` shows exactly the change that `-m 1` will invert. Parent 1 is whatever branch was checked out when the merge was made — normally the receiving branch.
  • What does reverting a merge leave unchanged in the history graph?
    The ancestry. The merge commit stays, so every commit from the merged branch remains an ancestor of your branch; Git still regards them as merged even though their content is gone from the tree. Only the tree changed. That mismatch between content and ancestry is what causes trouble when the branch is merged again later.
  • When would reverting the individual commits be better than reverting the merge?
    When you want the branch to be re-landable normally later, or when only part of what the merge brought in is bad. Reverting the individual commits keeps the ancestry story straightforward and avoids the special handling a reverted merge needs, at the cost of more revert commits to manage.

Undoing a merge is like undoing a confluence of two rivers: you have to say which river you consider the main channel before "remove the other one's water" means anything.

saying these in an interview costs you the question

  • Passing a commit sha to -m instead of a parent number
  • Thinking -m picks which changes to keep by content
  • Assuming Git can infer the mainline from the branch you are on
  • Believing reverting a merge removes it from history
  • Expecting the reverted branch to merge normally afterwards

context