Use git revert -m for a merge that already reached a shared branch; use git reset only when you can safely rewrite history. That single rule prevents a lot of angry Slack threads. GitHub reported more than 100 million developers using the platform in 2023, so merge mistakes are not rare edge cases; they are normal team work with sharp corners [source: GitHub].
TLDR
git revert -m safely undoes a merge by creating a new commit, so the public branch history stays intact. For example, if a team of 8 merges a feature with 17 commits into main and QA finds a payment bug 20 minutes later, git revert -m 1 <merge_sha> is usually the safer fix. git reset moves the branch pointer backward, which is fine on your laptop but risky after others have pulled the branch. If the merge is public, revert it; if it is private and unpushed, reset may be faster.
Why merge commits are different
A normal commit has one parent. A merge commit has two or more. That small detail is why git revert <sha> is not enough for a merge commit.
Git needs to know which parent should be treated as the mainline. That is what the -m flag means. It does not mean “message.” It means mainline parent.
For a common merge into main, parent 1 is usually the branch you were on, such as main. Parent 2 is usually the merged feature branch. So this command says, “keep parent 1, undo what the merge brought in from the other side.”
git revert -m 1 <merge_commit_sha>
Honestly, it feels like Git could explain this better at the moment you make the mistake. Instead, it gives you a message about needing a mainline parent, and now you are stuck counting parents while production is already smoking.
Git revert vs git reset
git revert adds history. It creates a new commit that reverses the effect of an older commit. The old commit stays there. The merge commit stays there too. Everyone’s clone can move forward cleanly.
git reset moves history. It changes where your branch points. With --hard, it also changes your working tree and can throw away local changes. That is useful when cleaning up local work. It is dangerous on shared branches.
- Use
git revert -mwhen the merge has been pushed tomain,develop, or a release branch. - Use
git reset --hardwhen the bad merge exists only locally and no one else has based work on it. - Avoid reset on public history unless your team has agreed to force push and clean up the fallout.
How git revert -m works
Say your history looks like this:
A---B---C---M main
\ /
D---E feature
M is the merge commit. It has two parents. Parent 1 is C. Parent 2 is E. If M introduced a bug, you can run:
git checkout main
git pull
git revert -m 1 M
git push
This creates a new commit, often called something like Revert “Merge branch feature”. The code from the feature branch is removed from main, but history still shows what happened.
That matters. CI systems, code review tools, deployment records, and teammates all keep a consistent story. Nobody has to repair their local branch because you rewrote the last hour of work.
How to find the right parent
Before running git revert -m, inspect the merge commit:
git show --no-patch --pretty=raw <merge_commit_sha>
You will see lines like:
parent 1111111
parent 2222222
The first parent is -m 1. The second is -m 2. In most day-to-day cases, reverting a feature merge from main means -m 1. Still, check it. Guessing here is how a 30-second fix becomes a 45-minute archaeology session.
You can also use:
git log --oneline --graph --decorate
This makes the branch shape easier to read. Visual tools in GitHub, GitLab, Bitbucket, or an IDE can help too.
When reset is the better choice
git reset is not bad. It is just often used in the wrong place.
If you merged the wrong branch locally and have not pushed it, reset is clean and quick:
git reset --hard HEAD~1
That moves your branch back one commit and updates your files to match. If the merge was the last commit, it is gone from your local branch.
There are three common reset modes:
--soft: moves the branch pointer but keeps changes staged.--mixed: moves the branch pointer and unstages changes. This is the default.--hard: moves the branch pointer and discards working tree changes.
The annoying part is that --hard is brutally literal. If you had uncommitted work, it may disappear from the working tree. Sometimes reflog can save you. Sometimes not without pain.
Why reset can hurt a shared branch
Imagine this sequence:
- You merge
feature checkoutintomain. - You push it.
- Four teammates pull
main. - You run
git reset --hard HEAD~1. - You force push.
Now their local histories include a commit that no longer exists on the remote branch. They may see confusing pull errors. They may need to rebase, reset, or repair local branches. Someone may accidentally push the merge back again. It drives me crazy that one force push can create 10 tiny cleanups across a team.
That is why revert is the calmer choice. It does not pretend the merge never happened. It adds a new commit that says, “We undid this.”
The hidden cost of reverting a merge
git revert -m has one tricky side effect. Git records that the merge happened. If you later try to merge the same feature branch again, Git may think much of it is already included.
This surprises people. You reverted the code, so you expect a later merge to bring it back. Git thinks in commits, not intentions.
The usual fixes are:
- Revert the revert when the feature is ready again.
- Cherry-pick selected commits if only part of the feature should return.
- Rebase or rebuild the branch if the feature needs heavy changes.
For example:
git revert <revert_commit_sha>
That re-applies the changes removed by the earlier revert. Then you can add fixes on top.
A practical decision guide
Use this simple checklist before undoing a merge:
- Has the merge been pushed? If yes, prefer
git revert -m. - Has anyone pulled it? If yes or maybe, do not reset unless the team agrees.
- Is the merge only local? Reset is fine.
- Do you need an audit trail? Revert gives you one.
- Will the feature come back later? Plan for a revert of the revert.
Safe command sequence for a public merge
Here is a careful flow for undoing a bad merge already on main:
git checkout main
git pull origin main
git log --oneline --graph
git show --no-patch --pretty=raw <merge_commit_sha>
git revert -m 1 <merge_commit_sha>
git status
git push origin main
If conflicts appear, resolve them like normal merge conflicts:
git status
git add <fixed_files>
git revert --continue
If you realize you chose the wrong parent before completing the revert, stop:
git revert --abort
The clean rule to remember
Revert is for shared mistakes. Reset is for private cleanup. When the bad merge is already visible to others, git revert -m 1 protects the team from broken history and surprise force-push problems. When the mistake is still only on your machine, git reset is faster and cleaner. Pick based on who has seen the commit, not on how badly you want it gone.

