Abort a conflicted merge

How-to Last tested 26 October 2026

Use git merge --abort to cancel a merge that is still in progress and attempt to restore its starting state. The most predictable starting point is committed work with no pending edits. Aborting is not a promise to recover every edit made before or during a conflicted merge, and it does not undo a merge commit that already finished.

Confirm that a merge is actually in progress

A conflict message is a reason to inspect the state before choosing a command. A merge, a rebase and a stash application can all involve conflicts, but they do not have the same operation to cancel. This page concerns a merge that stopped before producing its final commit.

The merge manual describes abort as an attempt to reconstruct the earlier state, with explicit limits when uncommitted work was present at the start or edited again during the merge1. That boundary matters more than the command’s short spelling. If the repository contains valuable conflict-resolution work, preserve what you need before cancelling it; the demonstrated abort returns to the earlier tracked content.

Begin by reading status and identifying the current operation, the affected paths and any unrelated work. Do not replace this inspection with a hard reset or a branch switch. If the merge has already completed, stop and choose a separate history-preserving or history-rewriting plan according to how that commit is used.

Run the complete example in an empty directory

The example creates a base commit, then changes the same line differently on two branches. It starts the merge from a clean committed state, so the conflict is intentional and the expected restoration has a known reference. A second pass demonstrates the newer resolved-path staging option without treating it as a substitute for abort.

Download the complete example. Run it with Git 2.56.0 in an empty directory. It creates a new merge-example directory and keeps all work local.

#!/bin/sh
# SPDX-License-Identifier: MIT
set -eu
export TZ=UTC LC_ALL=C
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_NAME='Example Author'
export GIT_AUTHOR_EMAIL='author@example.invalid'
export GIT_COMMITTER_NAME='Example Author'
export GIT_COMMITTER_EMAIL='author@example.invalid'
export GIT_AUTHOR_DATE='2026-10-25T12:00:00+00:00'
export GIT_COMMITTER_DATE='2026-10-25T12:00:00+00:00'
test "$(git version)" = 'git version 2.56.0'

mkdir merge-example
cd merge-example
git init -q --template= --initial-branch=main --object-format=sha1
git config commit.gpgsign false
git config core.autocrlf false
printf 'base\n' > note.txt
printf 'original unrelated file\n' > later.txt
git add note.txt later.txt
git commit -qm 'Base note'
git switch -qc topic
printf 'topic version\n' > note.txt
git commit -qam 'Topic change'
git switch -q main
printf 'main version\n' > note.txt
git commit -qam 'Main change'
test -z "$(git status --porcelain=v1)"
head_before=$(git rev-parse HEAD)
file_before=$(git hash-object note.txt)

if git merge --no-edit topic; then
  printf 'Expected a conflict\n' >&2
  exit 1
fi
test -n "$(git ls-files --unmerged)"
printf 'Conflict: merge is paused with unresolved entries\n'
git status --short
git merge --abort
test "$(git rev-parse HEAD)" = "$head_before"
test "$(git hash-object note.txt)" = "$file_before"
test -z "$(git status --porcelain=v1)"
if git rev-parse --verify -q MERGE_HEAD; then
  printf 'Merge still in progress\n' >&2
  exit 1
fi
printf 'Abort: original commit and file restored; no merge in progress\n'
git log -1 --format='%h %s'

if git merge --no-edit topic; then
  exit 1
fi
printf 'chosen resolution\n' > note.txt
printf 'unrelated local work\n' > later.txt
git add --resolved -- note.txt later.txt
test -z "$(git ls-files --unmerged)"
test "$(git show :note.txt)" = 'chosen resolution'
test "$(git status --porcelain=v1 -- later.txt)" = ' M later.txt'
test -z "$(git diff --cached -- later.txt)"
git diff --cached -- note.txt
printf 'Resolved staging: resolution staged; unrelated tracked edit unstaged\n'
git merge --abort
test "$(git rev-parse HEAD)" = "$head_before"
test "$(git hash-object note.txt)" = "$file_before"
test "$(cat later.txt)" = 'unrelated local work'
printf 'Second abort: tracked file restored; unrelated tracked edit remains\n'

Create a conflict with a known starting point

The topic branch changes the original line to topic version. The main branch independently changes it to main version. Neither change is merely a file edit by the time the merge begins: both have their own commits. The script checks that main has a clean status and remembers its current commit and file-content hash.

Those recorded values are the reference for the later assertions. The script does not decide that an abort was successful solely because its exit code was zero. It compares the resulting commit and tracked-file content with the exact state saved before the merge began.

The merge is expected to stop with a conflict. If it unexpectedly completes, the script fails instead of continuing through a different scenario. It then confirms that unresolved index entries exist and prints status. This makes the prerequisite for the next action visible: there is an unfinished merge to cancel.

Abort and inspect the restored state

The first abort returns the example to the remembered main commit and restores note.txt to the remembered file hash. Status becomes clean, and the script confirms that no merge head remains. These observations jointly establish the result for this clean-start case.

The branch still has its independent change. The other branch still has its own change. Aborting did not combine them, select a preferred version for a new merge commit or delete the other branch. It cancelled this attempt to combine their histories and returned the tracked file to its pre-merge content.

That distinction helps with the next decision. If the branches still need integration, examine why their edits conflict and decide on the intended final content before trying again. If the merge was started against the wrong branch, inspect the intended destination and starting point. Repeating the same merge without changing the decision will reproduce the same kind of conflict.

Resolved staging is a different operation

The second pass starts the same conflict again. It writes a chosen resolution to note.txt and edits the already tracked later.txt. Both paths are explicitly listed in the staging command to show that --resolved considers only unmerged paths: it stages the marker-free conflict path while leaving the unrelated tracked edit unstaged2.

The script checks that unresolved entries have gone, the staged copy contains the chosen resolution, and later.txt is modified but unstaged. It prints the selected staged diff for inspection. These checks do not commit the merge or establish that the chosen text is correct for a real application.

The script then aborts again. The conflicted file returns to its earlier content, while the unrelated tracked edit remains in this case. That edit began after the merge started. This does not establish recovery of edits that already existed before an unclean merge start, or edits to paths the merge must update.

The option is marker-based, not a resolution validator. Binary and deletion conflicts can have no markers and still require your decision; resolve them yourself before staging explicit paths. Check git diff --cached before committing. Use abort when the task is to abandon the unfinished merge rather than continue it.

Keep cancellation separate from other work

A useful cancellation check has a before state, an after state and a clear boundary. In this example, the committed start and remembered file hash provide that boundary. In a real repository, an undocumented mixture of old edits, conflict-resolution attempts and unrelated additions makes the question harder to answer after the fact.

The staged and working differences guide explains why the index and working file need separate attention. The branch-switching guide covers preserving unfinished edits when changing tasks. Neither should be used as an improvised replacement for the operation that is actually in progress.

Finally, remember that forgetting a merge and restoring its starting state are different actions. The merge reference describes its quit option as leaving the index and working files in place1. This page does not run that alternative or suggest it as an interchangeable cancellation command. Choose based on the desired final state, not on whether a command makes a message disappear.

Not covered on this page

Sources

  1. Git 2.56.0 merge reference. ↩ ↩2

  2. Git 2.56.0 resolved-path staging reference. ↩

How we test: methodology. Found a mistake? Corrections.