A merge commit has two parents, so git revert cannot guess which set of changes you want undone. That is the entire reason it refuses without -m. Use git revert -m 1 <merge-sha> to keep the branch you merged into and discard what came in — then remember that Git now considers those changes permanently rejected, which is the part that bites three weeks later.
The error message is honest but unhelpful if you have not met it before: error: commit ... is a merge but no -m option was given. The flag is easy. The consequence is not, and almost nobody mentions it until the re-merge silently does nothing.
Table of contents
- Why the -m flag exists
- The part that bites later: the branch will not re-merge
- Fix one: revert the revert
- Fix two: rebuild the branch
- revert vs reset, briefly
- Doing this against a deployed branch
- How this fits the rest of the stack
- FAQ
Why the -m flag exists
An ordinary commit has one parent. Reverting it means “apply the inverse of the diff between this commit and its parent” — unambiguous.
A merge commit has two parents. Parent 1 is the branch you were on when you ran git merge (usually main). Parent 2 is the branch you merged in (usually the feature branch). “Undo this merge” could mean either “remove the feature work” or “remove everything main did independently”, and those are wildly different diffs.
So Git makes you say it out loud. -m 1 means “treat parent 1 as the mainline, keep it, and revert everything parent 2 brought in.” That is what you want roughly always.
# Find the merge commit
git log --oneline --merges -5
# Inspect its parents before you touch anything
git show --no-patch --format='%h %p %s' a1b2c3d
# a1b2c3d 9f8e7d6 4c5b6a7 Merge pull request #482 from feat/billing
# ^parent1 ^parent2
# Revert it, keeping mainline (parent 1)
git revert -m 1 a1b2c3d
The first SHA in %p is parent 1. If you are on main and you merged a feature branch, parent 1 is main. Confirm rather than assume — a merge made during a rebase or from the other direction can flip them.
The part that bites later: the branch will not re-merge
This is the real content of the keyword, and most results bury it. Reverting a merge does not undo the merge in Git’s bookkeeping. The merge still happened. The commits from the feature branch are still ancestors of main.
What you added was a new commit that happens to contain the inverse diff. So when you later fix the feature and run git merge feat/billing again, Git looks at the ancestry, sees those commits are already merged, and brings in nothing. The branch merges “successfully” and your files do not change.
A prototype without logs is a mystery with a URL; a revert without this knowledge is a merge that quietly does nothing. Teams lose an afternoon to it.
There are two ways out, and they suit different situations.
Fix one: revert the revert
If the feature branch is unchanged and you simply want the work back, revert your revert commit. It is not a joke — it is the documented approach.
# You reverted the merge here
git revert -m 1 a1b2c3d # creates commit r3v3rt1
# Later, to bring the feature back:
git revert r3v3rt1 # ordinary commit, no -m needed
# Now new commits on feat/billing merge normally again
git merge feat/billing
The revert commit has one parent, so no -m this time. The history reads a little strange — “Revert ‘Revert …’” — but it is accurate, and accuracy in history is worth more than tidiness.
Fix two: rebuild the branch
If the feature needs real changes anyway, do not fight the ancestry. Make a fresh branch from current main and cherry-pick or rewrite the work onto it.
git checkout -b feat/billing-v2 main
git cherry-pick 4c5b6a7^..4c5b6a7 # or re-apply the work properly
# fix whatever caused the revert
git push -u origin feat/billing-v2
New commits, new SHAs, no ancestry problem. This is usually the better call when the revert happened because the feature was actually broken rather than badly timed.
revert vs reset, briefly
Reach for revert when the commit is already pushed and other people have it. It adds a new commit, so nobody’s history is rewritten and nobody has to recover.
Reach for reset only when the merge is still local. git reset --hard HEAD~1 on an unpushed merge is clean and leaves no trace, which is exactly why it is dangerous once the commit is shared.
git revert -m 1 <sha>— safe on shared branches, leaves a record, creates the ancestry problem abovegit reset --hard ORIG_HEAD— undoes a merge you have not pushed, no ancestry problem at allgit merge --abort— for a merge that is still in progress with conflicts on the table
ORIG_HEAD is worth knowing: Git sets it before a merge or reset, so git reset --hard ORIG_HEAD is the immediate undo when you have just merged and instantly regretted it.
Doing this against a deployed branch
The mechanical part is one command. The operational part is what your deploy pipeline does when the revert lands, and that is the bit worth having wired up in advance.
If the merge went out to production, reverting the merge produces a new commit that triggers a new build. That build has to succeed for the revert to mean anything — a revert that fails CI leaves the broken version live, which is the worst of both states.
Being able to roll back the running deploy independently of the Git history is what makes this calm. On RunxBuild each deploy is kept, so you can roll back to the previous deploy while you sort the branch out at your own pace, with the build log for the failed version and the runtime log for the failing requests in the same place.
How this fits the rest of the stack
Reverting a merge is two facts: pass -m 1 to name the mainline, and know that the branch will not merge again until you revert the revert or rebuild it. Everything else follows from those. If the merge is still local, git reset --hard ORIG_HEAD is simpler than any of this.
The wider question is what happens to the running service while you sort the history out. Deploy history, rollback, and build logs are the difference between a bad merge being a ten-minute annoyance and an outage. If you are costing out where that project runs, the RunxBuild hosting calculator puts the service, the database, the storage, and the bandwidth on one page as separate line items rather than one number you have to trust.
Useful related references:
- Undoing a Merge in Git: Revert, Reset, and the Trap That Follows
- Revert a Single File in Git: Restoring One Path Without Touching the Rest
- How to Rename a File in Linux: mv, rename, and git mv
- Services on RunxBuild
FAQ
What does -m 1 actually mean in git revert?
It selects which parent of the merge commit is treated as the mainline — the side you want to keep. -m 1 keeps the first parent, which is the branch you were on when you ran the merge (normally main), and reverts everything the second parent brought in. -m 2 does the opposite. Check with git show --no-patch --format='%p' <sha>, which lists the parents in order.
Why does my branch not merge again after I reverted the merge?
Because the original merge is still in the ancestry. Git sees those commits as already merged and brings in nothing new. Your revert added an inverse diff but did not remove the merge from history. Either revert the revert commit, or create a fresh branch and re-apply the work so it carries new SHAs.
Can I revert a merge that has already been pushed?
Yes, and revert is specifically the right tool for that case. It creates a new commit rather than rewriting history, so nobody else has to reset or force-pull. Rewriting a pushed merge with reset and a force push is what causes the messy recovery for everyone else on the branch.
What is the difference between git revert and git reset for a merge?
revert adds a new commit containing the inverse changes and is safe on shared branches. reset moves the branch pointer and discards commits, which is clean but only safe if you have not pushed. As an immediate undo for a merge you just made locally, git reset --hard ORIG_HEAD is the shortest path.
How do I undo a merge that has conflicts I have not resolved yet?
If the merge is still in progress and unresolved, git merge --abort returns the working tree to the state before you started. That is different from reverting — nothing was committed, so there is nothing to revert. Use --abort while the conflict markers are still open.