A merge commit landed on a shared branch and it has to come out. git revert merge is the command for exactly that: it removes the effect of a merge commit by adding a new commit on top, without deleting or rewriting anything already pushed. That rules out git merge --abort, which only works while MERGE_HEAD still exists — long gone once the merge is committed and pushed — and it rules out a hard reset, which would force everyone who already pulled the merge to reconcile diverged history. Almost every guide to this command mentions the -m flag and stops there. Almost none explain how to actually verify which parent number is correct before you run it, or what happens if you merge the same branch again afterward. This guide covers both, in depth, plus the GitHub Revert-button mechanics, the exact error messages you'll hit if you get the flag wrong, and how to confirm the rollback landed correctly before you push. For reverting an ordinary, single-parent commit, see the companion guide on reverting a commit — merge commits are the one case that guide only touches briefly, and this one owns.

The Fast Answer: Revert a Merge Commit in One Command

One command is all it takes to undo a merge that's already pushed — safely, with no history rewrite:

git revert -m 1 <merge-commit-hash>

That's the whole move in the overwhelming majority of cases: parent 1 is the branch you were on when you ran the merge — main, typically — and -m 1 keeps that line of development intact while undoing everything the merge brought in from the other side. That's what a git revert merge actually is once you strip away the jargon — a rollback git merge, in plainer terms, that leaves the merge commit itself alone and just cancels out what it introduced. What that one command doesn't answer up front: whether "1" is actually correct for your merge, and what happens if someone tries to merge the same branch again later. Both get a full section below — the second one trips up teams who think a revert is the end of the story.

Why a Merge Commit Needs -m to Revert

An ordinary commit has exactly one parent, so git revert <hash> has exactly one possible diff to invert — no ambiguity, no extra flag, which is the whole case covered in the guide to reverting a single commit. A merge commit is different by construction: it has two parents (or more, for a rare octopus merge), one for each branch that came together. "The diff of this commit" stops being a single well-defined thing the moment there are two parents to diff against, and git refuses to guess which one you mean. That refusal is the entire reason -m exists — git's official git revert documentation calls the value it takes the mainline parent number. It's not an optional performance flag, it's the answer to a question git cannot answer on its own: relative to which parent should the merge's changes be undone?

Try it without the flag and git stops you immediately, before touching anything:

git revert a1b2c3d
# error: commit a1b2c3d is a merge but no -m option was given.

The full mechanics of that error, and its mirror-image opposite, get their own section further down. First, the part that actually decides whether your revert does the right thing: figuring out which parent number to pass.

Finding the Right Parent Before You Revert

This is the step most tutorials skip entirely, and it's the one that actually matters. Don't assume parent 1 is correct just because it usually is — confirm it. Git records a merge commit's parents in the exact order they were combined, and four commands expose that order directly.

# Print both parent SHAs, in order: parent 1 first, parent 2 second
git log --pretty=%P -1 <merge-hash>

# Same information via git show
git show --format=%P -s <merge-hash>

# The raw commit object — look for the "parent" lines, in the order they appear
git cat-file -p <merge-hash>
# tree 8f3d2c1...
# parent 9f8e7d6...    <- parent 1
# parent 3d81e0f...    <- parent 2
# author ...

# Visual confirmation of which branch is which
git log --graph --oneline --all

The order is not a convention you have to trust — it's a direct consequence of how git merge builds the commit. Parent 1 is always whatever HEAD pointed to when the merge started: the branch you were checked out on. Parent 2 is the tip of the branch named in the merge command — the one being brought in. Run git merge feature while on main and parent 1 is main's pre-merge tip, parent 2 is feature's tip, full stop. If a teammate instead had feature checked out and ran merge master into branch to sync it, the order for that commit is flipped — which is exactly why checking with %P beats assuming.

Parent order is fixed by which branch was checked out A main checked out at merge time F feature named in git merge feature M parent 1 parent 2 git log --pretty=%P -1 <merge-hash> 9f8e7d6 (A, parent 1) 3d81e0f (F, parent 2) first hash printed is always parent 1
git log --pretty=%P prints parent hashes in commit order, not alphabetical or branch-name order — the first hash is always parent 1, the branch that was checked out when git merge ran.

git log --graph --oneline --all is the fastest sanity check once you already have a theory about which side is which: the branch whose line runs straight through the merge commit without a kink is parent 1; the branch whose line visibly joins in from the side is parent 2. On a busy repository with several concurrent branches, narrow the graph to just the two commits you care about with git log --graph --oneline <merge-hash>~3..<merge-hash> before trusting what you're looking at.

Choosing -m 1 vs -m 2: The Decision Rule

With the parent order confirmed, the choice is mechanical. -m 1 keeps parent 1 as the baseline and undoes whatever parent 2 uniquely introduced — this is what you want in roughly 95% of real merges, because parent 1 is almost always the long-lived branch (main) and parent 2 is the short-lived feature branch you actually want gone. -m 2 does the opposite: it keeps parent 2 as the baseline and undoes what parent 1 contributed instead. That's the inverted case, and it only applies when the parent order itself is inverted from the usual pattern — for example a merge commit created while a feature branch was checked out and main was merged into it, rather than the other way around. Guessing here instead of checking with the commands from the previous section is the single most common way this goes wrong.

Which parent number keeps the branch you want? Confirm parent order git log --pretty=%P -1 <hash> Is parent 1 the branch you want to keep as the baseline (usually main)? the standard case, ~95% of merges Yes No — inverted -m 1 — keep mainline -m 2 — keep the other side
Don't assume parent 1 is main — confirm the order first, then match the flag to whichever parent is actually the branch you want left standing.

The wrong parent doesn't fail loudly — it fails by producing a commit that looks plausible and is completely wrong. Run -m 2 on a standard "feature merged into main" commit and git tries to undo main's changes relative to the feature branch instead of the feature's changes relative to main. On any branch with real history, that diff is enormous — it reverses weeks or months of unrelated main-line development, not the small feature you meant to pull back out. That size mismatch is the tell: if the revert's diff looks nothing like the size of the feature you're trying to remove, you almost certainly passed the wrong parent number. Catch it with a diff before you push — the verification section below covers exactly how.

Running git revert -m 1 Against the Merge Commit

Once the parent number is confirmed, running it is the easy part:

git revert -m 1 a1b2c3d
# opens your editor with a default message:
# Revert "Merge branch 'checkout-flow-redesign' into main"
#
# save and close -> the revert commit is created
# a1b2c3d itself is untouched and stays in history

The same flags that apply to a plain commit revert work here too — --no-edit to skip the editor and accept the default message, --no-commit to stage the inverse changes without committing yet, --continue and --abort if the revert hits a conflict mid-way. The full rundown of those is in the revert commit options guide; nothing about them changes for a merge beyond the required -m.

One syntax trap worth calling out directly: typing git revert m1 a1b2c3d — no dash — does not work. Without the leading -, git treats m1 as a revision name to revert, not as a flag, and fails with something like fatal: bad revision 'm1' or an "unknown revision" error, because no branch or tag named m1 exists. The flag is -m followed by the parent number as a separate argument — -m 1, with a space, or the glued short form -m1 if you prefer; either works, but the dash is not optional.

The Re-Merge Trap: Why the Branch Won't Come Back

Here's the part almost nobody explains, and the reason this article exists. Say the revert landed clean, shipped, and later someone — maybe you, maybe a teammate who doesn't know the history — decides the feature should come back after all, and does the obvious thing:

git checkout main
git merge feature-branch
# Already up to date.

Nothing happens. Not "conflicts," not "nothing to commit" — git genuinely believes there is nothing new to bring in, and the feature's changes stay reverted. This isn't a bug and it isn't rare; it's the mechanical, guaranteed result of how git merge decides what to do.

git merge does not compare file contents to figure out what's missing. It walks commit ancestry to find a merge base, then merges in whatever commits exist on one side but not the other. The first merge already made every commit on feature-branch an ancestor of main — that's what a merge commit is. Reverting the merge commit removes the file changes but does not remove that ancestry; feature-branch's tip is still, provably, an ancestor of main's current tip. So when you run git merge feature-branch again with no new commits on that branch, git finds zero commits that aren't already ancestors, and correctly — by its own logic — reports nothing to do. The revert's removal simply stays in effect, silently, because there's no new commit for a merge to carry across.

Reverting undoes the files — not the ancestry the merge created M git merge feature feature's commits become ancestors of main R git revert -m 1 M undoes the file changes — ancestry is untouched ? git merge feature (again, no new commits) Already up to date. feature's tip is still an ancestor git merge walks ancestry, not file contents — it finds no new commit to bring across
R undoes what M changed in the files but leaves feature's commits as ancestors of main — so a plain re-merge finds nothing new and silently leaves the revert in effect.

This is documented behavior, not a guess — Linus Torvalds wrote the canonical explanation of it in Git's own howto directory, and it's worth reading in full if this is happening on a repo you maintain: "How to revert a faulty merge". The short version matches what's above: reverting a merge and then re-merging the same branch is not how you bring a reverted feature back, because git's merge logic only looks at ancestry, and the ancestry never changed.

Escaping the Trap: Revert the Revert or Rebuild the Branch

Two ways out, and they trade off differently.

Option 1 — revert the revert. Since the revert commit is itself just an ordinary, single-parent commit, undo it the normal way:

git revert <revert-commit-hash>
# no -m needed — the revert commit has one parent

This creates a new commit that reapplies the feature's original content, byte for byte — no re-merge involved, so the ancestry trap never comes into play. History ends up with an honest, fully auditable three-step record: merged, reverted, restored. The full mechanics of reverting a revert — including the doubled commit message git generates — are covered in the dedicated revert-a-revert guide; this is the same operation, just applied to a merge's revert commit instead of a plain one. It's the safer default: nothing gets rewritten, it works on any shared branch, and it's a single command.

Option 2 — rebuild the branch. Rebase feature-branch onto the current tip of main so every one of its commits gets a brand-new SHA. New SHAs are not ancestors of anything yet, so a subsequent git merge feature-branch finds real, genuinely new commits to bring in and applies them properly — the trap only applies to commits git has already seen. This is a good moment to also squash the branch's commits into a cleaner unit before the second merge. The catch: rebasing rewrites the branch's history, which is only safe if nobody else has based work on it, or requires a coordinated force-push if they have.

Pick option 1 when the feature branch is done and you just want the exact same content back with minimal ceremony. Pick option 2 when the branch is going to keep evolving and you'd rather future merges behave normally without another double-revert dance every time.

GitHub Undo Merge: What the Revert Button Actually Does

On a merged pull request's page, GitHub shows a Revert button. Clicking it does not touch the original merge commit — it opens a brand-new pull request whose diff is the exact equivalent of running git revert -m 1 against the merge commit locally. That's the real mechanism behind a github rollback merge: a second PR, not an un-merge. You still have to review and merge that new PR for the rollback to actually take effect — clicking Revert alone doesn't change anything on the target branch by itself.

The Revert button opens a PR — it doesn't touch the merge commit Merged PR merge commit M on main [ Revert ] button click New PR opens diff = git revert -m 1 against merge commit M not merged yet review Required checks same as any PR on a protected branch merge R rollback lands Merge commit M is never deleted or rewritten at any step clicking Revert alone changes nothing until the new PR is merged
Four steps from click to rollback — and the original merge commit sits untouched through all of them; only the new PR's own merge actually changes the branch.

Branch protection doesn't create a side door here — on a protected main, that revert PR has to pass the same required reviews and status checks as any other pull request before it can merge. If you'd rather drive it from the terminal instead of the button, the CLI path is the same command covered above followed by a normal PR:

git revert -m 1 <merge-hash>
git push origin revert-checkout-flow-redesign
gh pr create --title "Revert: checkout flow redesign" --body "Reverts #482"

This is also the fallback for the one case the button can't handle: the Revert button is greyed out or missing entirely when the revert can't be applied cleanly — usually because commits landed on the target branch after the merge that now conflict with undoing it, or because the same branch was merged more than once. When that happens, run git revert -m 1 locally, resolve the conflict by hand, then push a branch and open the PR yourself with a normal push to a remote branch and gh pr create as shown above. Either route ends in the same place: a github rollback merge adds a commit to main, it never erases the one that's already there.

Whole Merge vs Individual Commits: What to Revert

Reverting the merge commit with -m 1 undoes the entire feature atomically — every file change the branch introduced disappears in one commit, and the log keeps a clean pointer: "this shipped in commit X, got pulled back out in commit Y." That's the right default when the branch was one cohesive unit of work.

The alternative is reverting individual commits from inside the merged branch instead — git revert <commit-sha> for each one you want gone, no -m needed since those are ordinary single-parent commits once you're targeting them directly rather than the merge itself. This lets you keep some of the work and drop the rest, but it gets error-prone fast: if a later commit renames a function or extends a type that an earlier commit introduced, reverting only one of them leaves the tree in a state that never actually existed and often won't even compile. The decision rule is simple — revert individual commits only when you've confirmed they're genuinely independent of each other; when in doubt, revert the whole merge commit and bring back the parts worth keeping as fresh commits with a cherry-pick from the original branch instead of trying to selectively undo pieces of an interdependent chain.

If what you actually regret is a single ordinary commit rather than a whole merged branch, the undo last commit guide covers the lighter-weight tools for that — amend, soft reset, or a plain revert with no -m involved at all.

Common Errors: No -m Option and Wrong Mainline

Three failure messages cover almost every mistake made trying to git revert merge commit history. All of them are refusals to run — none of them leave a half-finished revert behind.

git revert a1b2c3d
# error: commit a1b2c3d is a merge but no -m option was given.
# fatal: revert failed

You pointed revert at a commit with more than one parent and didn't say which one is the baseline. A merge commit's diff is ambiguous on its own, and git refuses to guess rather than silently pick one. The fix is adding -m with the parent number you confirmed using the commands from the parent-finding section above — most often -m 1.

git revert -m 2 9f8e7d6
# error: commit 9f8e7d6 does not have parent 2
# fatal: revert failed

The mirror-image mistake: you asked for a parent the commit doesn't have. That happens two ways — you passed -m 2 against an ordinary single-parent commit, or you passed -m 3 against a merge that only has two parents. Git checks the parent list before it computes anything, so nothing is staged and nothing is committed. Confirm the parent count with git log --pretty=%P -1 <hash> and re-run with a number that exists.

One asymmetry catches people out here, and it's worth knowing because it fails quietly rather than loudly. Running git revert -m 1 against an ordinary non-merge commit does not error — it just succeeds. Every commit that has a parent at all has a parent 1, so git honours the request and reverts the commit exactly as if you'd omitted the flag. That means a copy-pasted -m 1 aimed at the wrong hash won't warn you; it produces a perfectly valid revert of something you didn't mean to touch. The hash is what you need to double-check, not the flag. (Older git releases rejected this case with mainline was specified but commit ... is not a merge, which is why that message still shows up in tutorials — current git, verified on 2.50.1, no longer emits it.)

git revert -m 0 a1b2c3d
# error: option `mainline' expects a number greater than zero

Parent numbering is 1-indexed, not 0-indexed. There is no parent 0, and git rejects the value during option parsing before it looks at the commit at all. If you came to git from an array-indexed mental model, this is the one-line correction: the first parent is -m 1.

git revert -m vs git reset for a Merge

Same undo intent, opposite blast radius — the whole rollback git merge decision in one paragraph. git revert -m 1 adds a commit — reversible, safe on any branch, and it never touches history anyone else already has. git reset --hard back to before the merge moves the branch pointer and discards the merge commit outright; that's only safe if the merge commit never left your machine, and even then it wipes any uncommitted work with no exceptions. Once the merge is pushed, reset plus a force-push is the wrong tool, full stop — it rewrites shared history and forces everyone else to reconcile a diverged branch.

Same goal, opposite blast radius git revert -m 1 <hash> M R history only grows Additive — narrow blast radius Safe on pushed / shared branches Plain git push, no force needed M stays in history, untouched git reset --hard HEAD~1 M branch pointer moves back Rewrites history — wide blast radius Only safe if M never left your machine Force-push required once shared Uncommitted work destroyed, no exceptions
revert -m 1 only ever adds a commit; reset --hard deletes one and moves the branch pointer — the same reason one is push-safe and the other requires everyone else to reconcile a diverged branch.

Four ways to undo a merge, side by side — what each does to history, whether it's safe once the merge is pushed, when to reach for it, and what happens if you try to bring the branch back later.

Command What it does to history Safe on a pushed/shared branch When to use Re-merge consequence
git revert -m 1 <hash> Adds a new commit that undoes the merge's changes; original merge commit stays in history Yes — purely additive, no rewrite Merge is already committed and pushed, or you want a fully auditable undo either way Falls into the re-merge trap — commits are already ancestors, so merging the branch again reports "Already up to date"
git reset --hard HEAD~1 Moves the branch pointer back one commit and discards the merge commit outright No — rewrites history, forces a force-push and reconciliation for anyone who has it Merge commit is still local only, confirmed with git log origin/main..HEAD None — the merge commit is gone, so a later merge of the same branch brings its commits in fresh
git merge --abort Discards the in-progress merge entirely; no commit is ever created N/A — nothing has been committed or pushed yet Merge is still unresolved, MERGE_HEAD still exists, conflict markers still in your files None — there's no merge commit, so trying again later is a completely fresh merge
GitHub Revert button Opens a new PR containing the equivalent of git revert -m 1; merging it adds a revert commit Yes — same additive mechanism as the CLI command, plus normal PR review Merged PR needs undoing and you want the review trail on the rollback itself Same re-merge trap as the CLI revert — the button doesn't remove ancestry either

The full breakdown of what each reset mode preserves and destroys is in the dedicated git reset --hard guide — this isn't the place to re-teach it. And if the merge hasn't even finished committing yet — conflict markers still in your files, MERGE_HEAD still present — reverting is the wrong tool entirely; git merge --abort is cheaper and cleaner, since it cancels the merge before any commit, revert or otherwise, needs to exist at all.

Verifying the Rollback Before You Push

Confirm the revert actually did what you meant before it ships, especially after any -m decision that wasn't completely obvious.

# Compare the merge commit's pre-merge state against your current tip —
# should be empty or minimal if the revert fully undid the feature
git diff <merge-hash>^1 HEAD

# Quick summary of exactly which files the revert commit touched
git show <revert-commit> --stat
Three checks before you push a merge revert 1. Sharpest check git diff <merge>^1 HEAD empty/minimal = fully reverted 2. Scope check git show <revert> --stat confirms which files it touched 3. Visual side-by-side before-merge vs after-revert file, line by line
The two terminal commands catch a wrong -m number or an unexpectedly large diff; the visual pass is the last line of defense for exactly which lines changed on files too long to read as raw hunks.

git diff <merge-hash>^1 HEAD is the sharpest check: it compares the branch's state right before the merge against where you are now. If the revert was clean and nothing else changed since, that diff should be empty or close to it — anything large or unexpected there is the same size-mismatch signal covered earlier, and it's worth catching before you push, not after. Both commands emit standard unified diffs; the unified diff format guide walks through reading the +/- hunks if the notation is unfamiliar.

For a revert that touches a lot of files, scrolling through terminal hunks is slow and easy to misread. This is where a visual side-by-side check pays for itself: paste the "before the merge" and "after the revert" versions of a file into Diff Checker, and every added, removed, and unchanged line is color-coded and lined up automatically — no squinting at plus and minus signs. It runs on Monaco Editor's diff engine entirely in your browser, with a "Show Diff Only" mode that collapses everything unchanged so a long file doesn't bury the two or three regions that actually matter, plus Alt+Down and Alt+Up to step through every change one at a time. Nothing you paste leaves your machine — there's no upload, no account, and no git integration to configure; it's a plain two-pane text compare that works on whatever you feed it, which is exactly what git show already extracted for you. For comparing arbitrary file versions beyond a single revert, the git diff between two files guide covers the full range of scoping flags.

Frequently Asked Questions

What's the difference between -m 1 and -m 2 when reverting a merge?

-m 1 tells git revert to treat parent 1 — the branch you were checked out on when you ran git merge, usually main — as the baseline, and undo everything the other parent brought in. That is correct in roughly 95% of merges: you were on main, you merged a feature branch in, and now you want the feature's changes gone while main's own history stays intact. -m 2 flips it: it keeps parent 2 as the baseline and undoes what parent 1 uniquely contributed instead. Only use -m 2 when the parent order is actually inverted from the usual pattern, and confirm that with git log --pretty=%P -1 <hash> rather than guessing — picking the wrong number reverts the opposite set of changes.

What happens when you merge the same branch again after reverting it?

Nothing — and that's the trap. Git decides what a merge needs to bring in by walking commit ancestry, not by diffing file contents. Because the feature branch's commits already became ancestors of main the first time it was merged, they are still ancestors after you revert; the revert commit removes the file changes but not the ancestry. Running git merge feature-branch again finds no new commits to bring across and reports "Already up to date," even though the revert already undid every line the branch introduced. The fix is to revert the revert commit, or rebuild the branch so its commits get new SHAs before merging again.

Can you undo a revert of a merge commit?

Yes. A revert is an ordinary commit, so you revert it the same way you'd revert anything else: git revert <revert-commit-hash>, no -m flag needed since the revert commit itself has only one parent. This creates a second commit that reapplies the original merge's changes, and it also sidesteps the re-merge trap — reverting the revert restores the feature's content directly instead of relying on a fresh merge, which would find nothing new to bring in.

How do I revert a merged pull request on GitHub using the UI?

Open the merged pull request and click the Revert button near the bottom of the page. GitHub does not delete or touch the original merge commit — it opens a brand-new pull request whose diff is the equivalent of running git revert -m 1 against the merge commit locally. You still have to review and merge that second PR for the rollback to take effect, and on a protected branch it goes through the same required checks as any other PR. If the button is greyed out or missing, the revert cannot be applied cleanly; fall back to running git revert -m 1 locally, resolving the conflict, and opening the PR yourself with gh pr create.

What does "commit is a merge but no -m option was given" mean?

It means you ran git revert against a commit that has more than one parent without telling git which parent to treat as the baseline. A merge commit's diff is ambiguous on its own — undo relative to which side? — so git refuses to guess and stops with that error instead of reverting the wrong thing. The fix is adding the -m flag with the correct parent number, most often git revert -m 1 <hash>, after confirming the parent order with git log --pretty=%P -1 <hash>.

Is it safe to revert a merge that's already been pushed?

Yes — that is exactly the case git revert -m 1 is built for. It adds a new commit that undoes the merge's changes instead of deleting or rewriting the merge commit, so a plain git push afterward works with no force-push and no history rewrite. Every clone that already has the merge commit simply pulls the revert commit like any other new commit. The unsafe move on a pushed merge is git reset --hard plus a force-push, which rewrites shared history and breaks every other clone of the branch.