git merge abort cancels an in-progress merge and puts your branch back exactly
where it was before you ran git merge — but only while the merge is still in
progress. Once a merge finishes and becomes a commit, git merge --abort stops
working entirely, and you need a different command depending on whether that commit is still
local or already pushed somewhere else. This guide covers all three situations precisely: the
merge you haven't committed, the one you have but haven't pushed, and the one that's already
shared. It also covers the parts most guides skip — git merge --quit,
MERGE_HEAD mechanics, autostash behavior, and what happens when you try to abort a
squash merge. If your merge went through fine and you just want to double-check what actually
changed, diffing two files across branches is
the fast way to confirm before you move on.
The Fast Answer: Three Situations, Three Commands
Three different situations get called "git cancel merge," "git undo merge," or "abort merge" in practice, and each one needs a different command. Mixing them up is the single biggest source of confusion in this area, so start here before anything else.
# 1. Merge is IN PROGRESS — conflict markers in your files, no commit made yet
git merge --abort
# 2. Merge is COMMITTED but not pushed — you have a merge commit only you can see
git reset --hard HEAD~1
# 3. Merge is COMMITTED and PUSHED — other people may already have it
git revert -m 1 <merge-commit-hash>
Check which situation you're in with one command: git status. If it reports "You
have unmerged paths" or "All conflicts fixed but you are still merging," you're in situation 1.
If git status shows a clean working tree and git log -1 shows a merge
commit at the tip, you're in situation 2 or 3, and the only question left is whether that commit
exists anywhere besides your machine — check with git log origin/main..HEAD; if the
merge commit shows up in that list, it hasn't been pushed, and situation 2 applies. Everything
below expands on why these three commands are the right ones, plus git merge --quit
as a fourth option that behaves differently from all three.
What "A Merge Is In Progress" Actually Means
Git doesn't track "merge in progress" as an abstract flag. It writes real files into your
repository's .git directory, and their presence or absence is the actual mechanism
that decides whether git merge --abort has anything to do.
# While a merge is unresolved, these exist in .git/
.git/MERGE_HEAD # commit(s) being merged in — this is the file --abort checks for
.git/MERGE_MSG # draft commit message, pre-filled with conflict markers if any
.git/MERGE_MODE # present for --no-ff merges, empty otherwise
.git/ORIG_HEAD # where HEAD pointed before the merge started
.git/AUTO_MERGE # (Git 2.35+) tree from the last conflict auto-resolution pass MERGE_HEAD is the one that matters most: it's the file that makes a merge "in
progress" in Git's own definition. It holds the commit hash (or hashes, for an octopus merge) of
whatever you're merging in. git commit checks for its existence — if it's there, the
commit you're about to make is treated as a merge commit with multiple parents; once the commit
completes, Git deletes MERGE_HEAD. That deletion is the exact moment
git merge --abort stops being available, because --abort's own
documentation defines it as failing "if there is no merge in progress," which Git checks by
looking for that file.
ORIG_HEAD is a separate, more general-purpose file — it's not merge-specific.
git merge, git reset, git rebase, and git pull
all overwrite it with wherever HEAD pointed right before that command ran. It's
useful as a one-shot "go back" reference (covered in the recovery section below), but only
immediately after the operation you care about — the next history-changing command clobbers it.
git merge --abort: The Command for a Merge You Haven't Committed
This is the command people mean almost every time they search "git cancel merge" or "git abort
merge with conflicts." It applies exactly when MERGE_HEAD exists — which is any point
between running git merge and running git commit to finish it,
including after every conflict is resolved and staged but not yet committed. Canceling the
conflict and canceling the merge are the same act, so there is no separate git cancel merge
conflict command to look for — the conflict only exists as part of the merge Git is holding open,
and clearing the merge clears it.
git merge feature-branch
# CONFLICT (content): Merge conflict in src/api/retry.ts
# Automatic merge failed; fix conflicts and then commit the result.
git merge --abort
# working tree and MERGE_HEAD are gone — back to pre-merge state
Mechanically, git merge --abort is documented as equivalent to
git reset --merge MERGE_HEAD when a merge is in progress. It does three things:
restores every tracked file the merge touched to its pre-merge content, removes
MERGE_HEAD, MERGE_MSG, and MERGE_MODE, and leaves
HEAD exactly where it was — no new commit, no branch change. Untouched files stay
untouched. If Git auto-stashed anything before the merge started (see the autostash section
below), that stash gets reapplied as the last step.
Git's own documentation for --abort includes a real caveat worth repeating exactly:
it "can fail to reconstruct the original (pre-merge) changes" if you had uncommitted changes to
files the merge itself needed to modify. In practice this is rare, since git merge
already refuses to start if it detects an unresolvable conflict between an uncommitted local
change and the incoming merge — but it's why running git status right after an
abort is a good habit, not paranoia. It also confirms the abort left you on the branch you
started from; checking the
current branch covers the other commands that answer that question when
git status output gets long.
Why Abort Sometimes Fails — and What to Do Instead
The single most common mistake with git merge abort: running it after the merge commit was already made. Git's answer is blunt and easy to grep for:
git merge --abort
# fatal: There is no merge to abort (MERGE_HEAD missing).
That error means exactly what it says — MERGE_HEAD is gone because the merge already
finished and became a real commit. --abort has nothing left to cancel; "abort" only
makes sense for something still in flight. Aborting a merge with conflicts works fine; running the
same command against a merge that already committed cleanly, or that you resolved and committed
minutes ago, does not — and this is the fork in the road that decides everything else in this
guide. There is no force abort merge flag to reach for either: --abort either finds a
MERGE_HEAD to work with or it doesn't, and no option overrides that check. The
vocabulary varies — git abandon merge, git stop merge and git exit merge all name the same intent
— but past this point the command doesn't, and it depends only on whether the merge commit left
your machine:
- Not pushed yet:
git reset --hard HEAD~1— see the section on undoing a committed merge. - Already pushed / shared:
git revert -m 1 <hash>— see undoing a pushed merge.
A less common variant of the same failure: running --abort from a squash merge.
git merge --squash never writes MERGE_HEAD in the first place — it's
covered separately below, because the fix isn't the same as the ordinary "already committed" case.
git merge --quit vs --abort: Keeping the Work, Dropping the Merge
git merge --quit, added in Git 2.23 (August 2019), is the option almost nobody knows about
and almost every other guide skips entirely. It solves a specific problem --abort
can't: you're mid-merge, you've resolved some or all of the conflicts by hand, and you want to
stop being "in a merge" — without losing the edits you just made to the working tree.
git merge --quit
# clears MERGE_HEAD, MERGE_MSG, MERGE_MODE
# leaves the working tree and index exactly as they are — no reset
The difference in one sentence: --abort resets the working tree back to pre-merge
state and then clears the merge metadata; --quit clears the merge metadata and
leaves the working tree alone. After --quit, git status no longer shows
you as mid-merge — but any files you edited during conflict resolution are still sitting there,
modified, exactly as you left them. You've stopped Git from treating the situation as an active
merge without throwing away a single keystroke of resolution work.
Reach for it when you've done real conflict-resolution work you don't want to redo, but you've
decided this isn't going to become a merge commit right now — maybe you want to commit the
resolved files as a regular commit instead, stash them for later, or split the work into a
smaller change first. --abort is for "this merge was a mistake, erase it."
--quit is for "stop calling this a merge, but keep what I already fixed." Most git
abandon merge advice only ever mentions --abort, which throws the resolution work out
along with the merge; --quit is the half that keeps it.
Undoing a Merge You Already Committed
Once git commit finishes a merge, MERGE_HEAD is gone and
--abort is off the table. If that merge commit is still local — you haven't pushed,
and nobody else can have pulled it — this is the simpler of the two post-commit situations,
because rewriting history is safe when only you can see it. It's also what people are after when
they search git undo last merge or git undo local merge: one merge commit sitting at the tip of a
branch nobody else has fetched. The mechanics are the same ones behind
undoing the last commit — a merge commit is still
just a commit at the tip — with one extra wrinkle: it has two parents, which is what changes the
answer the moment that commit reaches a remote.
# Confirm the merge commit is unpushed
git log origin/main..HEAD
# shows the merge commit if it hasn't reached the remote
# Remove it
git reset --hard HEAD~1
A search for "git reset merge" lands on both halves of this: git reset --merge is the
mid-merge form --abort runs internally, and git reset --hard HEAD~1 is
the post-commit one. The second moves your branch pointer back one commit — off the merge
commit, onto whatever was there before you ran git merge — and overwrites every
tracked file in the working directory to match. This is a genuinely destructive command: unlike
--abort's reset --merge behavior, plain --hard does not
preserve unrelated uncommitted changes. Anything uncommitted gets wiped, no exceptions, no
autostash safety net. If you have work in progress unrelated to the merge, commit or
stash it first. For the fuller picture of what --hard,
--soft, and --mixed each preserve, see the dedicated guide on
git reset --hard.
ORIG_HEAD works here too, immediately after the merge: git reset --hard
ORIG_HEAD does the identical thing to HEAD~1 right after a merge commit, since
Git set ORIG_HEAD to your pre-merge position when the merge ran. The difference
matters once you've run anything else since — another reset, a rebase, a pull — because each of
those overwrites ORIG_HEAD with its own "before" position, silently making it point
somewhere other than pre-merge. HEAD~1 stays correct regardless of what ran since,
as long as the merge commit is still the single most recent commit on the branch.
Undoing a Merge You Already Pushed: git revert -m
If the merge commit already reached a shared remote — anyone else could have fetched or pulled it
— git reset --hard is the wrong tool. Rewriting a branch other people have based work
on forces everyone to reconcile diverging history, usually with a confusing force-push. The safe
move is git revert, which adds a new commit undoing the change instead of deleting
the old one — the only git undo merge move that stays safe on a branch other people already have.
Merge commits need one extra flag that a plain commit revert doesn't. Try it without the flag and Git refuses outright:
git revert a1b2c3d
# error: commit a1b2c3d is a merge but no -m option was given.
That error exists because a merge commit has two (or more) parents, and "undo this commit" is
ambiguous without saying which parent to treat as the baseline. -m 1 tells
git revert to use parent 1 — by convention, the branch you were on when you ran
git merge (main, typically) — as mainline, and produce a commit that
removes everything the merge introduced relative to that parent. The
git-revert
documentation spells out the parent-numbering rule in full:
git revert -m 1 a1b2c3d
# creates a new commit undoing the merge's changes
# the original merge commit a1b2c3d stays in history, untouched
The merge commit itself is never deleted — revert only ever adds. History keeps a
permanent, honest record: the feature merged in at commit A, then got reverted at commit B. If you
later decide the feature should ship after all, reverting a revert of a merge needs its own extra
care around which parent is mainline the second time; the full walkthrough, including the exact
gotcha with re-merging after a revert, is in the
guide to reverting a revert commit.
Cancel Merge on GitHub: What the UI Can and Can't Do
"Cancel merge GitHub" almost always means one of two different requests, and GitHub's interface draws a hard line between them.
Before the merge button is clicked: canceling is trivial — close the pull request without merging it, or push more commits and merge later. This is the only case where cancel merge GitHub means what it sounds like: nothing has touched the target branch yet, so there's nothing to undo. Closing the PR leaves both copies of the branch alive, so deleting the local branch is a separate cleanup step once the work is genuinely dead.
After the merge button is clicked: there is no GitHub Merged badge button that
deletes the merge from history. What GitHub does offer, on a merged PR's page, is a
Revert button — and it does exactly what the section above described from the
terminal: it opens a brand-new pull request whose diff is the equivalent of
git revert -m 1 against the merge commit. You still have to review and merge that
second PR for the revert to take effect, as
GitHub's
own documentation on reverting a pull request confirms. The original merge commit is never
removed; a second commit on top cancels its effect. That's the actual mechanism behind "how to
revert a merged pull request" — a new PR, not an un-merge.
There is one narrow exception where GitHub does let you truly erase a merge from a branch's history: force-pushing to that branch directly (with sufficient permissions and no branch protection blocking it) after resetting locally with the commands from the two sections above. That's a repository-history rewrite, not a GitHub feature — GitHub just doesn't stop you from pushing it, unless branch protection rules do. On any branch with more than one contributor, that's the same "don't rewrite shared history" problem as the local case, just enforced through push permissions instead of good judgment.
Stashes, Autostash, and Squash Merges: The Edge Cases
Three edge cases trip people up specifically because most tutorials never mention them at all.
MERGE_AUTOSTASH. Since Git 2.27 (June 2020), merge.autoStash exists
as a config option — off by default, so you have to opt in. Once it's set (or you pass
--autostash directly), starting a merge with uncommitted local changes
no longer blocks the merge with "error: Your local changes... would be overwritten." Instead, Git
stashes those changes automatically before the merge starts. If the merge succeeds, the stash
reapplies automatically at the end. If you run git merge --abort, that same autostash
reapplies as the last step of the abort — your pre-merge uncommitted changes come back untouched,
exactly as if the merge never happened. This is genuinely useful and genuinely undocumented in
most guides: check whether you've enabled it with git config merge.autoStash.
# Enable autostash for every future merge
git config --global merge.autoStash true
# Or use it for one merge only
git merge --autostash feature-branch
Autostash is also where the git reset merge distinction earns its keep.
git reset --merge, the command --abort is built on, respects
MERGE_AUTOSTASH the same way. Plain git reset --hard, by contrast, does
not restore an autostash — if you abandon a merge with --hard instead of
--abort, check git stash list for a leftover autostash entry that never
got reapplied, since --hard skips that step.
Squash merges have no MERGE_HEAD at all. git merge --squash
feature-branch takes every commit on feature-branch, combines their changes
into your working directory and index as one staged changeset — and deliberately does not write
MERGE_HEAD, because a squash merge produces a single-parent commit, not a real merge
commit. Try to abort one the normal way and Git tells you there's nothing to abort, even though
you're clearly mid-operation with staged changes sitting there:
git merge --squash feature-branch
git merge --abort
# fatal: There is no merge to abort (MERGE_HEAD missing).
The fix is to treat it like any other staged-but-uncommitted change, not like a merge:
git reset --merge HEAD unstages the squash changes and restores tracked files, or
git reset --hard HEAD for the same effect when you don't care about preserving
anything else uncommitted. Either works because there's no MERGE_HEAD-gated state to
clean up — just ordinary staged changes against a normal, single-parent HEAD.
git stash and an in-progress merge don't mix the way people expect. Stashing the
conflict, aborting, then unstashing later sounds like a clean workaround, but it's less reliable
than it looks. Running
git stash while conflict markers are still unresolved in your files stashes the
conflicted state, but git status will still show you as mid-merge afterward in some
Git versions, because plain stash doesn't touch MERGE_HEAD. If you want
out of the merge and want to preserve your resolution attempts, git merge --quit
(covered above) is the more predictable tool than stashing a half-resolved conflict and hoping to
reconstruct it later.
Recovering From the Wrong Undo
Ran git reset --hard when you meant --merge? Aborted, then realized you
actually wanted that merge? As long as the commits existed at some point, git reflog
almost always gets them back — Git doesn't delete commit objects immediately, it just stops
pointing at them, and unreferenced objects stick around until garbage collection runs (default
gc.pruneExpire is 2 weeks for unreachable objects).
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# 9f8e7d6 HEAD@{1}: merge feature-branch: Merge made by the 'ort' strategy.
# ...
# Get the merge commit back
git reset --hard HEAD@{1}
# or, by hash, directly:
git reset --hard 9f8e7d6 git reflog lists every position HEAD has occupied in this local
repository recently, newest first, each one addressable as HEAD@{n}.
Find the entry from right before the reset or abort you regret, then git reset --hard
onto that entry's hash restores the branch to that exact state — files, index, and all. This works
for undoing an over-aggressive --hard reset, restoring a merge you aborted and then
wanted back, or recovering from almost any local history mistake, with one caveat: the reflog is
local-only, per-clone, and typically retained for 90 days by default
(gc.reflogExpire) — it's not a substitute for pushing real work to a remote.
If a plain git reset --hard discarded uncommitted changes that were never committed
anywhere — never part of any commit object — the reflog can't help, because there was never a
commit to point back to. That's the one case with no recovery path, which is exactly why the
earlier sections repeatedly flag --hard's lack of an uncommitted-work safety net
compared to --abort or --merge.
Reading the Merge Before You Abort It
Before you decide whether to abort, quit, or push through a conflict, it usually helps to actually
see the two versions of a conflicted file rather than parse <<<<<<<
markers in place. Git exposes both sides directly through its index, no merge tool required:
# Your side (stage 2) and the incoming side (stage 3) of a conflicted file
git show :2:src/api/retry.ts > /tmp/ours.ts
git show :3:src/api/retry.ts > /tmp/theirs.ts
# Or, if you already aborted and want to see what the merge would have changed
git show HEAD~1:src/api/retry.ts > /tmp/before.ts
git show HEAD:src/api/retry.ts > /tmp/after.ts :2:path and :3:path pull the "ours" and "theirs" blobs straight out of
the merge conflict's index stages — the exact two versions Git couldn't reconcile automatically.
Pasting both into a two-pane viewer with syntax highlighting reads faster than scanning inline
conflict markers, especially in a file with several separate conflict regions. Diff Checker, a
free Chrome extension, is built for exactly this: paste one version into each pane and it lines
them up with word-level highlighting across 17 languages, with a "Show Diff Only" mode that
collapses everything that didn't change so a long file doesn't bury the two or three lines that
actually conflict. Everything runs client-side in the browser — nothing gets uploaded, which
matters when the file in question is proprietary code you're not about to paste into a random web
form.
This isn't a git client and doesn't read your repository — it's a plain two-pane text compare, and
you feed it whatever git show already extracted. That's deliberately narrow: for
checking out the branches involved in the first place, see
checking out a remote branch, and for a
broader walkthrough of comparing arbitrary file versions from the command line,
git diff between two files covers the full set
of flags. Once you've resolved a conflict and want a last look before committing, the same
ours/theirs extraction trick works after a
cherry-pick conflict too — the
stages are populated the same way.
Every Command, Side by Side
Every git merge abort option and alternative from this guide in one table — what each actually does, when it's the right call, and exactly how much uncommitted work it risks.
| Command | What it does | When to use | Risk to uncommitted work |
|---|---|---|---|
git merge --abort | Resets tracked files to pre-merge state, clears MERGE_HEAD/MSG/MODE, reapplies any autostash | Merge is unresolved or resolved-but-uncommitted (MERGE_HEAD still exists) | Low — preserves unrelated pre-merge uncommitted changes |
git merge --quit | Clears MERGE_HEAD/MSG/MODE only — leaves working tree and index exactly as they are | You've done real conflict-resolution work and want to stop being "mid-merge" without losing it | None — nothing in the working tree is touched |
git reset --merge MERGE_HEAD | The explicit form of what --abort runs internally, mid-merge | Same as --abort; rarely typed directly, useful to know it's the same mechanism | Low — same preservation behavior as --abort |
git reset --hard ORIG_HEAD | Moves HEAD and working tree back to wherever they were immediately before the merge ran | Right after a completed merge commit, before anything else touches ORIG_HEAD | High — discards all uncommitted changes, no exceptions |
git reset --hard HEAD~1 | Moves the branch pointer back one commit and overwrites the working tree to match | Merge is committed but not pushed — nobody else could have it | High — discards all uncommitted changes, no exceptions |
git revert -m 1 <hash> | Adds a new commit that undoes the merge's changes; original merge commit stays in history | Merge commit is already pushed or shared with others — never rewrite shared history | None — purely additive, doesn't touch the working tree beyond the revert's own changes |
git checkout -m . | Re-checks-out conflicted paths, restoring Git's automatic (non-conflicting) merge result and re-inserting conflict markers where needed | You edited a conflicted file's markers by hand and want to throw away just that edit, not the whole merge | Low — scoped to files passed, not the whole merge state |
For the canonical, source-level reference on every flag covered here, see the official git-merge documentation and git-reset documentation. One case in this area isn't an undo at all: if the merge itself was right and only its commit message came out wrong, amending the last commit rewrites that message without touching either parent.
Frequently Asked Questions
How do I undo a merge in Git?
It depends what stage the merge is at. Mid-merge, with conflict markers still in your files,
run git merge --abort to throw away the merge and go back to exactly where you
were. If the merge already completed and produced a merge commit, and you haven't pushed it,
git reset --hard HEAD~1 removes that commit and puts you back on the pre-merge
state. If the merge commit is already pushed and other people may have it, don't rewrite
history — run git revert -m 1 <merge-commit-hash> instead, which creates a
new commit that undoes the merge's changes without deleting the original commit.
What's the difference between git merge --abort and git reset?
git merge --abort only works while a merge is actively in progress — it reads
MERGE_HEAD, which Git deletes the moment the merge finishes (successfully or as
a commit). It's roughly equivalent to git reset --merge MERGE_HEAD: it restores
tracked files to their pre-merge state and preserves unrelated uncommitted changes that
existed before the merge started. Plain git reset --hard HEAD~1, by contrast,
works on a merge that's already committed — it moves the branch pointer back one commit and
wipes the working directory to match, discarding any uncommitted changes with no exceptions.
Once a merge is committed, --abort refuses to run at all, with "fatal: There is no merge to
abort."
How do I revert a merged pull request on GitHub?
GitHub's pull request page shows a Revert button after a PR merges, but it doesn't un-merge
anything — it opens a new pull request containing the equivalent of git revert -m
1 against the merge commit. Merging that new PR adds a commit that undoes the changes;
the original merge commit stays in history. There is no GitHub UI action that deletes a merge
after the fact once it's on a shared branch — that's what git revert exists for.
Before the merge happens, closing the PR without merging is the actual cancel.
What happens to uncommitted changes when I abort a merge?
If you had uncommitted changes before running git merge and you enabled autostash
(merge.autoStash, available since Git 2.27 but off by default), Git stashes them
as MERGE_AUTOSTASH and reapplies that stash
once git merge --abort finishes, so they come back untouched. Without autostash,
Git refuses to start a merge that would overwrite those changes in the first place. Uncommitted changes
that appeared during the merge itself — your resolved or half-resolved conflict edits — are
discarded; --abort resets tracked files to their pre-merge contents. Untracked files Git
didn't need to touch during the merge are left alone either way. The one documented edge case:
if you had uncommitted changes to files the merge itself needed to touch, Git's docs warn
--abort can fail to perfectly reconstruct the original state — rare, but worth a
git status check afterward.
How do I handle a merge conflict without aborting?
Open each file git status lists under "Unmerged paths," resolve the conflict
markers (<<<<<<<, =======,
>>>>>>>) by hand or with git checkout --ours
<file> for your side and --theirs for the incoming one, then git add
<file> for each resolved file and git commit with no message
argument to let Git generate the merge commit message. Reading both versions of a conflicted
file side by side before touching the markers — with git show :2:path for your
side and git show :3:path for theirs — makes the resolution faster than parsing
markers directly in a lot of cases.
Can I abort a merge after resolving conflicts but before committing?
Yes. As long as MERGE_HEAD still exists in .git — which is true
right up until you run git commit — git merge --abort works, even
if every conflict is already resolved and staged. It discards the staged resolutions along
with everything else and returns you to the pre-merge state. This is the same command whether
you're bailing at the first conflict marker or after resolving all of them and having second
thoughts.