Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

Undoing a Merge in Git: Revert, Reset, and the Trap That Follows

Sean

Platform Writer

Aug 08, 2026
8 min read

How you undo a merge depends entirely on whether you have pushed it. Not pushed: git reset —hard HEAD~1 and it never happened. Already pushed: git revert -m 1 , which adds a new commit undoing the changes — and creates a subtle problem when you later want to merge that branch again.

Undoing a Merge in Git: Revert, Reset, and the Trap That Follows

That second problem is the part worth understanding before you run the command, because it produces a merge that appears to succeed and brings in nothing.

Table of contents

If you have not pushed

Reset moves the branch pointer backwards, and the merge disappears from history entirely:

git reset --hard HEAD~1

Use —hard when you want the working tree to match, discarding any uncommitted changes. Use —soft to keep the changes staged, which is useful when you want to redo the merge differently.

A safer form that does not depend on counting commits:

git reset --hard ORIG_HEAD

Git sets ORIG_HEAD before operations that move HEAD substantially, including merge. So immediately after a merge, ORIG_HEAD is exactly where you were, with no arithmetic.

The rule: reset is for history nobody else has. Once a merge is pushed and someone has pulled it, resetting means rewriting shared history, which forces everyone to repair their clones.

If you have pushed: revert with -m

Revert creates a new commit that undoes the changes, leaving history intact:

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

The -m flag is required for merge commits and it is the part people get wrong. A merge commit has two parents, and git cannot know which one you consider the mainline:

  • -m 1 — the first parent, which is the branch you were on when you merged. This is what you want in essentially every case: keep main as it was, discard what the feature branch brought.
  • -m 2 — the second parent, the branch that was merged in. This discards the mainline’s changes instead, which is almost never the intent.

To check which is which before committing to it:

git log --oneline -1 <merge-commit>^1   # first parent
git log --oneline -1 <merge-commit>^2   # second parent

If the first shows the commit that was previously the tip of main, -m 1 is correct.

The re-merge trap

This is the consequence that catches people, and it is worth understanding rather than memorising.

Git decides what to merge by finding the common ancestor and taking what changed since. When you revert a merge, the merge is still in history — git still believes those commits are already merged. The revert is a separate commit that happens to undo their effects.

So when you fix the feature branch and merge it again, git looks at the ancestry, sees the original commits are already present, and brings in only what is new since. The reverted changes stay reverted. The merge succeeds, reports no conflicts, and your feature is still missing.

Two ways out.

Revert the revert. Straightforward when the branch was fine and the revert was the mistake:

git revert <the-revert-commit>

Rebuild the branch. Better when the branch genuinely needed changes. Rebase it onto the current mainline so it has new commit hashes with no merge relationship to the reverted ones:

git checkout feature-branch
git rebase main
# resolve, then merge as normal

The rebase produces fresh commits that git has never seen merged, so the merge behaves normally.

Choosing between them

A short decision procedure:

  1. Has anyone else pulled it? If no, reset. If yes or unsure, revert.
  2. Is it on a shared branch like main? Revert, always. Rewriting main’s history is a coordination event that costs everyone time.
  3. Do you need the audit trail? Revert keeps the record that the merge happened and was undone, which matters in a regulated or reviewed context.
  4. Is it your own unpushed work? Reset. It is cleaner and there is no cost.

The bias should be strongly toward revert on anything shared. A revert that turns out to be unnecessary is easy to revert again. A force-push that surprises five people takes an afternoon to clean up.

Undoing a fast-forward merge

A wrinkle: if the merge fast-forwarded, there is no merge commit at all. Git just moved the branch pointer forward, so the history is a straight line and there is nothing with two parents to revert.

In that case reverting means reverting the individual commits, or resetting if it has not been shared:

# Revert a range of commits, newest first
git revert --no-commit <oldest>^..<newest>
git commit -m "Revert feature X"

The —no-commit flag stages all the reversions and lets you make one commit rather than one per reverted commit, which keeps history readable.

This is one of the arguments for merging with —no-ff on shared branches. Always creating a merge commit means every merge is a single revertible unit, and the history shows which commits arrived together.

Recovering from a mistake

If you reset and immediately regret it, the commits are not gone. Git keeps a reflog of everywhere HEAD has been:

git reflog

That lists recent positions with their hashes. Find the one from before the reset and return to it:

git reset --hard HEAD@{1}
# or by hash
git reset --hard abc1234

The reflog is local and keeps entries for 90 days by default, which makes almost every local git mistake recoverable. It is the single most reassuring thing to know about git, and the reason a hard reset on your own branch is much less dangerous than it feels.

The exception is uncommitted work. The reflog tracks commits, so anything that was never committed and was destroyed by a —hard reset is genuinely gone. That is the one case worth being careful about — commit or stash before resetting.

How this fits the rest of the stack

The recurring theme is that undoing something is easy when a previous good state is addressable and awkward when it has to be reconstructed. That holds beyond git: a deploy you can roll back by selecting the previous build is a very different situation from one you have to rebuild by hand. Deploying from GitHub on RunxBuild covers building from a repository so each deploy is tied to a commit and the previous one stays selectable. When you are sizing what those deploys run on, the RunxBuild hosting calculator itemises the service, database, and bandwidth.

Useful related references:

FAQ

How do I undo a merge that has not been pushed?

git reset —hard HEAD~1, or more safely git reset —hard ORIG_HEAD, which git sets automatically before a merge so you do not have to count commits. Only do this on history nobody else has pulled.

What does the -m 1 flag mean in git revert?

It tells git which parent of the merge commit is the mainline. -m 1 is the branch you were on when merging, which keeps that branch and discards what was merged in. That is what you want in nearly every case.

Why does re-merging a branch after a revert bring in nothing?

The original merge is still in history, so git believes those commits are already merged and only brings in what is newer. Either revert the revert, or rebase the branch onto the mainline so it has fresh commits.

Should I use reset or revert to undo a merge?

Revert for anything already pushed or on a shared branch — it adds a new commit rather than rewriting history. Reset only for local work nobody else has pulled.

Can I recover commits after git reset —hard?

Yes, if they were committed. git reflog lists every recent position of HEAD, and you can reset back to one. Uncommitted changes destroyed by a hard reset are not recoverable, so commit or stash first.

#undoing a merge git#git revert merge#git reset#merge commit#git history