TL;DR
Since you want to abandon your version of the file, you want:
git checkout --theirs <file>
git add <file>
This tells Git that the correct resolution of combining your work (create a new version of file) and their work (create a different new version of file) is to use their version of the file.
Long, but please do read it
Given that Git's terminology is a problem (and it is, for a lot of people), what you should probably do here is learn the terminology. So let's take a look at that.
First, though, don't think about your repository as holding files. It does, but thinking about it this way won't get you anywhere—it's like trying to think about your house as holding outlets and doorknobs and so on. That's the wrong level to think about it. Your house contains rooms, and it's the rooms that have the doors that have the doorknobs, and the outlets that you plug appliances and your phone charger into, and so on. Just as the interesting level of "house" is "rooms", the interesting level of repository is commits. (Unlike a house—well, any practical house anyway—you just keep adding new commits to a repository, more or less forever, without ever throwing away any old ones. Well, mostly! It's hard to truly get rid of commits, but it can be done.)
So, your repository holds a big collection of commits. Each commit holds some files, and we'll get to that in a moment, but the repository itself is about the commits.
Most commits are pretty simple: they have a collection of files—a source tree snapshot—and some metadata, such as "this commit was made by A. U. Thor <thor@example.com>". One of the key pieces of metadata that almost every commit has is the hash ID of its parent commit. That's the commit that was there before Mr Thor (or whoever) made a new one. (Think of it as the door into the previous room, for instance.) So a commit:
- has some metadata—some extra information that's not part of the files in the commit; plus
- a complete snapshot of every file.
Git finds a commit by its hash ID, which is that big ugly seemingly-random thing that git log
prints:
$ git log
commit 3e5524907b43337e82a24afbc822078daf7a868f
Author: Junio C Hamano <...
These hash IDs are the true names of each commit. Every commit gets a unique one. They're quite useless to most humans, but Git uses them to find all the data that goes with each commit. This particular commit's parent is e66e8f9be8af90671b85a758b62610bd1162de2d
, so this commit—this 3e5524907...
says, in part, my parent is e668ef9be8...
. (One's eyes tend to glaze over when looking at these big ugly IDs. Fortunately, as humans, we can mostly ignore them.)
When you run git checkout somebranch
, what Git does is turn the branch name into a hash ID, and then extract all the files from that commit so that you can use them or work on them. It also sets its idea of the current commit to whatever that hash ID is, and sets its idea of the current branch name to the name you gave it. Running git status
will tell you on branch somebranch
, so now you are "on" that branch—which in part means that you have some particular commit hash ID checked out.
Now that you have some commit checked out, as you work within your repository, what you are doing is deciding what goes into some files that you will commit. Once you make each new commit, you will freeze all the files into that commit forever, as a new snapshot, in that form. If you remove a file and make a commit, you make a new commit / snapshot that has one fewer files, without changing any of the previous commits.
Git makes these snapshots from its staging area, which it also calls its index, or sometimes its cache. These are just three names for the same thing, either because it's so important (which it is) or because the first name that Linus used, "index", was such a bad name (in my opinion, it's not great but it's not really that bad). This staging area is normally almost invisible: you don't ever actually look at it directly. Instead, you do your work in what Git calls your work-tree or working tree or working directory. In this area, files are just files—they're not weird special Git entities.
In the index / staging-area, and in commits, files are weird and special: mostly, they're compressed to save space. In commits, they're also read-only: you cannot change any existing commit, not even one bit. You can overwrite the file that's in the index, by using git add
to copy the ordinary file from your work-tree area. That's how you get your changes done: you work on the file in the work-tree, then run git add file
.
Once you have all the files the way you want, and have copied them into the staging area to overwrite the previous copies, you run git commit
to have Git make a new commit. Git freezes the index copies of all the files—including the ones you didn't overwrite—and puts them all into the new commit. You are the author of the new commit. The new commit's parent commit is the commit you extracted earlier, by running git checkout
.
The same holds true when you are resolving a merge conflict. What you are doing is deciding which files, with which contents, will go into the next commit. But there are a few things that are special during this time.
The first is that the next commit you make will be a merge commit, which is to say, a commit with more than one parent. Instead of just saying my one parent is <big ugly ID>, it will say: my two parents are <first big ugly ID> and <second big ugly ID>. Git uses this internally to know that this was in fact a merge, and to be able to follow the history of the commits down both authors: your work, and whoever else's work, get combined at the merge, and you decide what the combined result is, but Git records for posterity the fact that both of your commits fed into it.
The second special thing that happens during this conflicted merge is that the index—remember, this is the staging area, the place where you're building your next merge commit to make—expands! Normally, your index holds just one copy of each file. But when you're merging, your index holds up to three copies of each conflicted file! You can have three README.md
files simultaneously. (I'll assume here that the conflict is about README.md
. Probably the file has some other name, but it all works the same.)
Technically, these multiple versions are called higher stage entries. Mostly you don't have to care about this; you just have to care about whether your index has this state for some file(s).
Your work-tree can't pull off this "multiple versions of one file" trick, and your eventual commit can only have one README.md
in it too. So this special index state, where it has more than one file, is something you have to fix. When you are done merging README.md
, you have the choice of having one README.md
in the index, or having no README.md
in the index. Whichever choice you make, when you run git commit
, Git will freeze that into the new commit.
This is what the linked question and answer is about. The index holds both versions of the file: yours and theirs. You can:
Use the work-tree to create a correct or combined file, and git add
the result. This overwrites all the conflicting multiple versions of README.md
, so that the index has just one README.md
.
Use git checkout --ours
to extract your version of README.md
from the index into the work-tree. The index still has all the versions, but now the work-tree file is your version.
Use git checkout --theirs
to extract their version of README.md
form the index into the work-tree. As before, the index still has all the versions, but now the work-tree file is their version.
If you choose one of these two git checkout
options, you can then go back to the first option—git add
—to copy the work-tree file, which is now just one of the two, into the index, wiping out the other versions.
Or, you can run git rm
on the file. This removes all the versions from the index, so now the index has no README.md
. If you are done resolving all conflicts, you can now git commit
; this new commit has no README.md
. As we already saw, this doesn't affect any previous commit, but it does mean that you declared that the correct way to combine your README.md
and their README.md
was to remove the file entirely.
If that's the wrong answer—if you want to keep their version, for instance—don't do that.
In any case, once you are all done resolving all files, you simply run git commit
as usual. This makes the new merge commit, and then changes the branch name itself—the hash ID stored under the name of branch you're on, that is—so that it remembers your new commit. That's how branches grow: a new commit has the old branch tip as its parent, or for a merge, the first one of its two parents. The new commit's newly-generated unique ID goes into the branch name. Hence if you had:
...--o--o--o <-- yourfix (HEAD)
\
o--o--o <-- origin/theirfix
and you ran git merge origin/theirfix
and got a merge conflict and resolved it and committed, you end up with:
...--o--o--o---M <-- yourfix (HEAD)
\ /
o--o--o <-- origin/theirfix
Note that the new commit (labeled M
here for Merge) is added on to your own branch name, yourfix
, by virtue of changing the hash ID stored under the name yourfix
. Since new commit M
remembers the hash ID that was stored in yourfix
just a moment ago, Git still has every file that was in that commit, ready to extract if you ever want it.
And, since M
remembers the same commit hash ID as origin/theirfix
, it's now safe for you to git push
commit M
to the Git over at origin
, and ask origin
to change its own theirfix
(which your Git is calling origin/theirfix
) to point to M
, as M
remembers their previous commit too. Git calls this a fast-forward operation when you run git push
—but let's leave that terminology for other questions.