You mainly have to do git reset
, which does a --mixed
reset. That does lose some information, which means that really, the answer is no. If there are no conflicts left to resolve, you can then do git add -u
or git add .
or similar to restore the lost information; but in any case, there are a few extra things to realize here to make this answer make sense.
First, Git doesn't really use your work-tree for much at all. To be sure, it does put files into your work-tree, and it will examine them for changes—for differences with what's in the index—under various conditions, such as git add -u
, or before clobbering the work-tree file due to another git checkout
. And, of course, git merge
writes merge conflicts into work-tree files in the familiar form:
$ cat file
some contents
a few lines
of contents just
<<<<<<< HEAD
to make some
||||||| merged common ancestors
to make
=======
to make
>>>>>>> branch
things more
interesting
(this is with merge.conflictStyle
set to diff3
, to add the ||||||| merged common ancestors
section so that you can see what was there before the merge conflict occurred—in this case what was there before was trailing whitespace, which I fixed on branch
by removing it, but on master
by adding more words).
The index that I mentioned above is where the real action is, though. This thing—this index, which Git also calls the staging area or the cache, depending on who / which part of Git is doing the calling—contains the version(s) of the file(s) that will go into the next commit.
Like the work-tree, the index is purely temporary. But it matters more, in a sense, to Git than the work-tree does. You can make a --bare
repository; such a repository has no work-tree, but it still has commits, and still has an index.1 Git can read a commit into the index, and write a new commit from the index, all without the need for a work-tree.
What's in the index is pretty simple, in the normal case. Every commit in Git saves a full, complete copy of every file. It saves that file in a special, read-only, compressed, Git-only form. I like to call these files freeze-dried. Each such file has a unique blob hash—unique to the file's content, that is. What's in the index is precisely these same freeze-dried files, or more precisely, the blob hashes. The key difference between the files in a commit and the files in the index is that the ones in the index can be overwritten with new, different freeze-dried files (different blob hashes). Well, that, and you can add new files, or remove existing files, again all in this freeze-dried form.
In any case, the act of making a new commit is so fast in Git because all it really does is take all the freeze-dried files that already in the index, and record their blob hashes in a commit (through more Git objects called trees but that's a mere implementation detail). The new commit gets a new, unique hash ID; the new commit remembers the previously-current commit's hash ID; and then the new commit becomes the current commit, and the head of the current branch if you're on a branch, by the act of writing the hash ID to HEAD
or through to the branch name.
Hence, in normal use, in a non-bare repository, there are always three active copies of each file. If you have a file named README.md
, you actually have:
HEAD:README.md
: this is the frozen copy in the current commit. You can't change it, nor can Git. (You can move HEAD
to another commit but that doesn't affect this commit's README.md
.)
:README.md
: this is the copy in the index. It's in the freeze-dried format, but you can replace it with a new copy any time.
README.md
: this is the only file you can see, taste, and smell—or whatever it is that you do with files on your computer. It's not in the freeze-dried format. It's in your work-tree, so you can do everything you would with any other file.
The main commands that affect these three files one at a time—ignoring the obvious git checkout <branch>
and the like that affect many at a time—are:
git checkout [commit] [--] paths
: gets individual files out of some commit, copying them into the index and then on into the work-tree.
git reset [commit] [--] paths
: copies individual files from some commit to the index, not touching the work-tree.
git add [--] paths
: copies individual files from the work-tree to the index, freeze-drying them during the copy.
When you start a merge, though, the index takes on a new, enlarged role. Instead of just one copy of each file in the index, there are now up to three copies. The three copies of file
in the example above are:
:1:file
: this is the file from the merge base commit, the merged common ancestors in mentioned in the work-tree conflicted file.
:2:file
: this is the file from the HEAD
commit.
:3:file
: this is the file from the other commit, in this case the commit at the tip of branch
.
There is no :0:file
at the moment—the name :file
is short for :0:file
—because there are these three non-zero stage numbered files.
The other thing that git merge
needs, to make a merge commit when you eventually resolve all conflicts and run git commit
or git merge --continue
, is the raw hash ID of the other commit. So Git has saved that into a file in .git
(.git/MERGE_HEAD
, as you mentioned). Hence, the fact that a merge is going on is recorded in two places: this .git/MERGE_HEAD
file, and the fact that there are unmerged index entries.
To stop merging, you must put all the index entries back to stage zero. That means you must choose some file(s) to move from stage 1, 2, and/or 3 into stage zero, or use the work-tree copy (via git add
) to put into stage zero, or use the HEAD
copy to put into stage zero.
Now, if they're already all at stage zero, either because you have resolved all the conflicts or because you had no conflicts and had merely used git merge --no-commit
to get into this state, this has one rather bad side effect: git reset --mixed
reads the HEAD
commit into the index, so that you lose all the already-added, resolved conflicts. But those added resolved conflicts are also in your work-tree right now, so you can (probably) put them back (but see last paragraph before footnote).
Any entries that aren't at stage zero mean, by definition, that there's an ongoing unresolved merge. There is no way to resolve that without destroying some of the ongoing unresolved merge.
The git reset --mixed
has the additional side effect of removing .git/MERGE_HEAD
, so that there is no longer an ongoing merge. So that solves the other problem. The big one is what to do with higher-stage index entries, and that particular problem can only be solved by destroying information. Whether that's information you meant to keep, or not, it has to be done.
If you have, for some reason, cleverly staged some version of file
that is neither in the HEAD
commit nor in the work-tree, this git reset --mixed
will clobber its hash. So you might want to use git checkout-index
to extract it to a temporary file somewhere, or at least record its blob hash and use git update-index --index-info
to restore that. Other than that, though, the answer is mainly just git reset
.
1Really, it probably should not have an index either. After all, when you add a work-tree to a non-bare repository, you actually add an index-and-work-tree both. But some internal parts of Git insist, or used to insist, on locking the index, so it has to have an index to lock.