Table of Contents

Introduction

If you've merged branches for any length of time, you've seen Git stop halfway with "Automatic merge failed; fix conflicts and then commit the result." Sometimes you're ready to sort it out right there. Other times the conflict is in a file you don't know, or it's late, or you realize you merged the wrong branch. git merge --abort is the way out: it drops the half-finished merge and puts your branch and files back where they were before you typed git merge.

In this article, we'll:

  1. Look at what a merge stopped on a conflict actually is, and how to tell you're in one
  2. Watch git merge --abort on a merge of feature into main that conflicted in styles.css
  3. Cover what it throws away, the older git reset --merge spelling, and when it refuses

What is git merge --abort?

git merge --abort cancels a merge that is still in progress. It resets the staging area and the working directory to the commit you were on when the merge started, removes the files Git uses to remember the merge, and leaves your branch exactly where it was. No commit is created and no label moves.

It only works while a merge is in progress. Once the merge has been committed, there is nothing to abort, and you undo it a different way.

What does "stopped on a conflict" mean?

When git merge can't combine two changes to the same lines, it doesn't guess. It merges everything it can, stages those files, and stops before making the merge commit. Three things tell you you're in that state.

First, Git writes a file called .git/MERGE_HEAD holding the hash of the commit you're merging in. That file is what makes the next git commit a merge commit with two parents, and it's also what --abort looks for.

Second, the conflicted file has conflict markers in it, showing both versions side by side. In our sample repo, styles.css looks like this:

<<<<<<< HEAD
header { display: grid; }
=======
header { display: flex; gap: 12px; }
>>>>>>> feature

The top half is main's version from "Switch the header to grid", the bottom half is feature's from "Space out the header links". Pro Git has a longer walk through reading these markers.

Third, git status says so, and even suggests the way out:

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Watch it happen

Our sample repo has a merge of feature into main stopped on a conflict in styles.css. main has "Switch the header to grid" (ada2e79) on top, feature ends in "Space out the header links" (791fd8e), and both changed the same line of styles.css. Here's git merge --abort:

  1. Before: main and HEAD point at ada2e79, and feature points at "Space out the header links" (791fd8e). Git stopped before making the merge commit, whose two parents would be ada2e79 and 791fd8e. MERGE_HEAD points at 791fd8e, which is how Git remembers what it was merging. styles.css is unmerged (in conflict), and the two files that merged cleanly, search.html and test_search.py, are staged from feature.
  2. Git deletes MERGE_HEAD and resets the index, dropping the conflict and the staged changes.
  3. styles.css goes back to its version in ada2e79, and search.html and test_search.py are removed from the working directory, because main doesn't have them yet. None of that work is lost: all three changes are still on feature, where the merge found them. Nothing is committed, and any work on the conflicted file is discarded too. Neither main nor HEAD moves.

Before and after

The two pictures are the same, and they should be. A stopped merge lives in the staging area and the working directory, not in the commit graph. The difference shows up in git status --short --branch. Before the abort, the two files the merge brought in cleanly are staged and styles.css is unmerged:

## main
A  search.html
UU styles.css
A  test_search.py

After, the status is clean:

## main

search.html and test_search.py are gone from the working directory again, since they only existed on feature. The abort itself prints nothing:

The raw git output, if you want to read along in text

git log --oneline --graph --all

before

* 791fd8e (feature) Space out the header links
* 1117a34 Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * ada2e79 (HEAD -> main) Switch the header to grid
| * 8c02d5b Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

after

* 791fd8e (feature) Space out the header links
* 1117a34 Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * ada2e79 (HEAD -> main) Switch the header to grid
| * 8c02d5b Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

Is it safe?

Caution git-sim pre-flight

Calls off the merge: HEAD stays on ada2e79, and the staging area and working directory are reset to it. Nothing is committed.

What you would lose

  • your edits to styles.css since the merge stopped (not recoverable)

The way back

  • Run the same merge again to get the conflict back; the edits themselves can't be recovered.

For your commits, yes. No commit is created, removed or moved, and feature is untouched. The one thing it throws away is whatever you did to the conflicted file after the merge stopped. If you had spent ten minutes combining the two header rules by hand, that edit is gone, because Git never stored it anywhere. The files that merged cleanly are thrown out too, but those are easy to get back by merging again.

I abort more merges than I finish on the first try. When a conflict lands in a file I haven't touched in months, I back out, read what the other branch did to it with git log -p feature -- styles.css, and then merge again knowing what I'm looking at. --abort has been around since Git 1.7.4 in 2011. Before that, the way out was git reset --merge, which still works.

When does it refuse?

The simplest case is when there's no merge to abort:

fatal: There is no merge to abort (MERGE_HEAD missing).

That usually means the merge already finished, or you're in the middle of a rebase or cherry-pick instead, which have their own --abort.

The trickier case is uncommitted work from before the merge. Git lets you start a merge with edits in files the merge doesn't touch, and --abort normally leaves those alone. But the git merge documentation warns that it "will in some cases be unable to reconstruct" them, especially if you kept editing after the merge stopped. When a file has unstaged changes that the abort would have to overwrite, it stops with an error instead of clobbering them. The safe habit is to commit or stash your changes before merging, so there's nothing for the abort to trip over.

How to undo it

There isn't much to undo, since nothing was committed. To get the conflict back, run the same merge again:

git merge feature

Git stops on styles.css exactly as before, and this time you can resolve it and finish with git merge --continue:

Useful forms

  • git reset --merge is the older command --abort is built on. With a merge in progress the two do the same thing.
  • git merge --quit forgets the merge but leaves the staging area and working directory as they are, conflict markers and all. It's for when you want out of the merge state but want to keep the files to pick through by hand.
  • git merge --abort works after a git pull that stopped on a conflict too, as long as the pull was merging rather than rebasing.

Try it on your repository

pip install git-sim
git-sim merge --abort

git-sim runs the abort in a copy of your repository and draws what would change, so your own conflicted merge stays exactly as it is.

Common questions

What does git merge --abort do?

It cancels a merge that stopped on a conflict. Your branch stays where it was, and the staging area and working directory go back to that commit. Nothing is committed.

Does git merge --abort delete my changes?

It discards anything you did to the conflicted files after the merge stopped. Uncommitted changes from before the merge are normally kept, but Git can't always rebuild them, so commit or stash before you merge.

What is the difference between git merge --abort and git reset --merge?

While a merge is in progress they do the same thing. --abort is the newer, clearer name, and it also checks that a merge is actually in progress.

What if git merge --abort says there is no merge to abort?

MERGE_HEAD is missing, so Git doesn't think a merge is in progress. Either it already finished (check git log) or you're in a rebase or cherry-pick, which git status will tell you.

Summary

In this article, we looked at what a merge stopped on a conflict is, watched git merge --abort put main back on ada2e79 without a single commit or label moving, and covered what it discards and when it refuses.

Next steps

git merge --continue is the other way out of a stopped merge, once you've resolved the conflict. git merge covers what a finished merge looks like.