Resolving different changes
When you run git merge
, you are—at least in the most general case1—telling Git to do its best to combine two different sets of changes. That is, both you, Mad Physicist, and someone else—I'll call him Joe—started from the same code, but you both made changes since then.
What Git does is, in essence, to run two git diff
s:
- Figure out which version you both started from. Let's say that was version
ba5eba5
.
- Figure out what version you have now. Let's say that's
HEAD
, because it is. :-) HEAD translates to some incomprehensible hash ID, but we can just say "HEAD".
- Figure out what version Joe has now. That's
temp-fix
, which, like HEAD
, turns into some incomprehensible hash ID, but again we can just say temp-fix
.
- Run
git diff ba5eba5 HEAD
: that's what you changed.
- Run
git diff ba5eba5 temp-fix
: that's what Joe changed.
- Try to combine the changes.
Where they don't overlap—e.g., if you deleted five lines starting at line 42, and Joe added three lines starting at line 127—Git just assumes that the right answer is to take both changes: delete those same five lines, and add those same three lines, to the base version.
Where they do overlap, you get a "merge conflict". Git writes both sets of changes into the file, with the markers. If you have merge.conflictStyle
set to diff3
, Git writes what was in the original base file in as well, as the "merged common ancestors". Setting this is a good idea, as it tells you what conflicted.
In this case, both you and Joe added code, without deleting anything at all. The "merged common ancestors" section is therefore empty.
If you want to take Joe's version, simply delete your added code and the merged common ancestors part, and keep Joe's version (and then delete the conflict markers). If you think your version is better, delete Joe's and keep yours.
1In many rather common cases, either you made changes and Joe didn't, or Joe made changes and you didn't. In this case Git just takes your version, or Joe's version, and calls it good-and-done. If the ba5eba5
common base itself is the same as your HEAD version, or the same as Joe's temp-fix, everything gets very easy. In one case (base = Joe, so you are ahead) there's nothing to merge, and in the other (base = you, but Joe is ahead) Git can do a so-called "fast forward" and just move right up to Joe's hash-ID.
Automating some or all of this
When you start the merge, you can supply -s
or -X
(or both) arguments. The names Git uses for these are, in my opinion, not very good (too confusing): -s
selects a strategy, and -X
passes a strategy option to the selected strategy (which usually defaults to the right one anyway, so usually you just want -X
). I like to say that X
stands for eXtenteded option, which is not a lot better, but at least avoids saying "strategy" yet again.
There is an ours
strategy, which is not what you want, and there are both ours
and theirs
extended options, which are sometimes what you want. So -X theirs
makes sense: it means if there is a conflict, favor Joe's over mine. Note the boldface part, though! If there is a conflict. If there is no conflict—if you deleted five lines near the top, and Joe did not touch those lines—Git still takes both changes. Only in the event of a conflict does Git automatically favor Joe.
Once Git stops with a conflict in the file, you can examine either your version, or Joe's version, using the same ours/theirs notation:
git checkout --ours -- somefile.pl
or:
git checkout --theirs -- somefile.pl
(the --
protects you if for some reason the file is named -f.pl
for instance: it's just a good habit to get into but not required for this case). But this means extract the whole thing from HEAD or from Joe's commit, which removes the other guy's changes. If you deleted five lines, and they should stay deleted, but you git checkout --theirs
to get Joe's version, those five lines are back.
Of course, if all the changes you made conflict with the ones Joe made, then extracting Joe's version will get you the same result that -X theirs
would. But that's a pretty big "if".
To reconstruct the merge conflict, you can:
git checkout -m -- somefile.pl
and then manually merge. (But see the next section.)
Once you have the final version of the file ready, you must git add
it to tell Git to copy the work-tree version into the index. The index currently has all three (base, ours, theirs) versions, in the three extra slots, with the "resolved" slot for the file unoccupied. Adding the resolved file fills in slot zero, where normal, resolved files go, and wipes out slots 1-3 where the unresolved files are kept.
Re-doing the merge of one specific file, with -X options
You can get Git to re-do the merge of one specific file without starting the whole merge over again with the appropriate extended option. It's annoyingly difficult, though. (This is one case where Mercurial has Git beat all hollow: what I'm about to show is all pre-packaged.)
If you extract all three versions of the file, you can now run git merge-file
on them, getting the effect of -X theirs
:
git checkout -- :1:somefile.pl
mv somefile.pl somefile.pl.base
git checkout --theirs somefile.pl
mv somefile.pl somefile.pl.theirs
git checkout --ours somefile.pl
git merge-file --theirs somefile.pl somefile.pl.base somefile.pl.theirs
To get the effect of -X ours
(favor our change if conflicting), use git merge-file --ours
. Once that's done, remove the .base
and .theirs
versions of the file.
(As usual, once you have resolved the conflict, you must git add
the file to copy it back into the index, at slot zero, to mark it as done.)