GitFixing a Bad Merge

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

Bash
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

Bash
cat .git/ORIG_HEAD
# jkl3456abc1234def5678...   ← the commit before the merge

Undo the merge using ORIG_HEAD

Bash
git reset --hard ORIG_HEAD
# HEAD is now at jkl3456 Previous good state
# Your working tree is clean and back to pre-merge state
Warning
`git reset --hard` discards all uncommitted changes. Run `git status` and stash anything you want to keep before using it.
ORIG_HEAD is overwritten
ORIG_HEAD is overwritten by the next merge, rebase, or reset. If you have run other operations since the bad merge, use the commit hash directly instead.
Scenario B: Merge is local — use the hash directly

Reset to the commit before the merge by hash

Bash
# 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
Warning
Never use `git reset --hard` on a merge commit that has been pushed to a shared branch. This rewrites history and breaks everyone who has already pulled. Use `git revert` instead.

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

Bash
# 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

Bash
# 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
Warning
If you revert a merge and later want to re-merge the same branch with fixes, you must first revert the revert commit. Otherwise Git thinks all those commits are already in the history and will not re-apply them.

Re-merging after a revert

Bash
# 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

Bash
# 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
  1. Was the merge pushed? No → use git reset --hard ORIG_HEAD (or the hash before the merge).

  2. Was the merge pushed? Yes, and is it on a shared branch? → use git revert -m 1 <hash>.

  3. Was the merge pushed to your own feature branch (no teammates)? → you can force-push after a reset, but warn anyone who reviewed it.

  4. Do you need to re-merge later after a revert? → revert the revert first, then merge the fixed branch.

Tip
After any merge undo, run your full test suite. Even a clean revert can leave the codebase in an unexpected state if the merge introduced changes to build configuration, database migrations, or shared dependencies.