Undoing a Bad Merge
You merged the wrong branch, merged too early, or merged with conflicts that produced incorrect code. This page walks through every scenario — from a merge that is still local to one that has been pushed to a shared remote — and explains the difference between reverting and resetting a merge commit.
Identify what kind of merge you need to undo
Find the merge commit
git log --oneline --graph -8 # * abc1234 (HEAD -> main) Merge branch 'feature-broken' # |\ # | * def5678 Add broken feature # | * ghi9012 Initial feature work # |/ # * jkl3456 Previous good state
The merge commit is abc1234. It has two parents: jkl3456 (main) and def5678 (the feature branch tip). This distinction matters when using git revert -m.
Scenario A: Merge is local (not pushed yet)
Git saves the pre-merge position of HEAD in a special reference called ORIG_HEAD. You can use this to reset back to before the merge instantly.
Check that ORIG_HEAD exists
cat .git/ORIG_HEAD # jkl3456abc1234def5678... ← the commit before the merge
Undo the merge using ORIG_HEAD
git reset --hard ORIG_HEAD # HEAD is now at jkl3456 Previous good state # Your working tree is clean and back to pre-merge state
Scenario B: Merge is local — use the hash directly
Reset to the commit before the merge by hash
# Find the commit that was before the merge git log --oneline -5 # abc1234 (HEAD -> main) Merge branch 'feature-broken' # jkl3456 Previous good state ← reset to here git reset --hard jkl3456 # HEAD is now at jkl3456 Previous good state
Scenario C: Merge has been pushed — use git revert
git revert -m 1 <merge-commit-hash> creates a new commit that reverses the changes introduced by the merge. The -m 1 flag tells Git which parent to consider the "mainline" — the branch you merged INTO.
Revert a pushed merge commit
# The bad merge commit git log --oneline -3 # abc1234 (HEAD -> main, origin/main) Merge branch 'feature-broken' # jkl3456 Previous good state # Revert the merge: -m 1 = keep mainline (the branch you were on) git revert -m 1 abc1234 --no-edit # [main xyz9999] Revert "Merge branch 'feature-broken'" # Date: ... # Push the revert commit git push origin main
Understanding the -m flag
A merge commit has two (or more) parents. The -m option specifies which parent is the "mainline" — the side you want to keep.
-m value | Meaning | When to use |
|---|---|---|
-m 1 | Keep parent 1 (the branch you were on when you ran git merge) | Most common — you merged a feature INTO main, and want to revert to main's state |
-m 2 | Keep parent 2 (the branch you merged in) | Rare — only if you want to preserve the feature branch changes and discard the base |
Identifying which parent is which
# Show the parents of the merge commit git cat-file -p abc1234 # tree ... # parent jkl3456 ← parent 1 (was HEAD when you ran git merge) # parent def5678 ← parent 2 (the branch you merged in) # author ... # committer ... # Merge branch 'feature-broken'
After reverting a merge — re-merging later
Re-merging after a revert
# The revert commit hash git log --oneline -5 # xyz9999 Revert "Merge branch 'feature-broken'" # abc1234 Merge branch 'feature-broken' ← original bad merge # jkl3456 Previous good state # First, revert the revert git revert xyz9999 --no-edit # [main aaa1111] Revert "Revert 'Merge branch 'feature-broken''" # Now merge the (fixed) feature branch git merge feature-broken-fixed
Difference between reverting and resetting a merge
Approach | How it works | History | Safe to push? | Best for |
|---|---|---|---|---|
git reset --hard ORIG_HEAD | Moves HEAD back to pre-merge position, discards merge commit | Rewritten — merge commit disappears | Only if not yet pushed | Local mistakes caught immediately |
git revert -m 1 | Creates a new commit that undoes the merge changes | Preserved — merge commit stays in log | Yes — additive, not destructive | Already-pushed merges on shared branches |
Verifying ORIG_HEAD before use
Check ORIG_HEAD is pointing at the right thing
# Show what ORIG_HEAD is git show ORIG_HEAD --stat # commit jkl3456... # Author: ... # Date: ... # # Previous good state # # (no changes — this is the commit before the merge)
Complete decision flowchart
Was the merge pushed? No → use
git reset --hard ORIG_HEAD(or the hash before the merge).Was the merge pushed? Yes, and is it on a shared branch? → use
git revert -m 1 <hash>.Was the merge pushed to your own feature branch (no teammates)? → you can force-push after a reset, but warn anyone who reviewed it.
Do you need to re-merge later after a revert? → revert the revert first, then merge the fixed branch.