You're expecting more of -X theirs
than Git can deliver.
Remember first that git merge
with the usual (recursive) strategy:
- finds the merge base;1
- diffs the merge base vs the current tip commit to get "our" changes; and
- diffs the merge base vs the commit you name—usually the tip of some other branch, but you can name any commit—to get "their" changes.
The two diffs have a series of diff hunks showing "what we did" and "what they did", and Git will now attempt to combine them so that we get one copy of each change. This means that if we fixed the spelling of a word on line 49 of some file, and they didn't, we get our fix. If they fixed the spelling and we didn't, we get their fix. If we both made the same spelling fix, we get the fix once, not twice, in spite of the fact that "fix the spelling" is represented as:
"First, delete an old line. Then, insert a new, different line."
and an overly naive application of "take each change" would try to delete a line twice, and/or insert two copies of the new line. Git (or any decent version control system's merge) notice that these two changes are the same, and just keeps one copy.
If two diff hunks in the two different diffs make different changes to the same original source area, though, Git normally just declares a merge conflict. It goes on to do whatever else it can, but it remembers the conflict, and stops at the end of the merge, leaving the conflict present in both the work-tree (in the familiar <<<<<<< ... >>>>>>>
form) and in the index.
Also known as the "staging area" or "cache", the index normally has one entry per work-tree file,2 but during a conflicted merge, it has, instead, up to three entries for each such file: one from the merge base, one from "ours", and one from "theirs". A "normal" (no conflict) entry goes in "staging slot zero" but this slot is not used for this file, this time. Instead, the merge-base version of the file goes in staging slot 1, and the other two versions in slots 2 and 3 (we can use --ours
and --theirs
to get them, rather than memorizing these slot numbers, but they are documented in gitrevisions
if you want to look them up at any time). We must resolve the conflict—often, just by editing the file in the work-tree—and then tell Git to replace the three copies in the "unmerged" slots in the index with a single copy in the normal, "ready to go into the next commit" stage-zero slot.
1This assumes there is a single merge-base commit. If there are multiple merge-bases, the action depends on the strategy. The default "recursive" strategy finds all the merge bases, and merges them to produce a single "virtual merge base". The "resolve" strategy simply picks one merge base at (apparently) random. The "octopus" strategy declares merge failure.
2More precisely, there is one entry per tracked file, with a special white-out entry for a file that is in the HEAD
commit but is scheduled for removal because of a git rm
. Truly untracked files have no index staging slots at all.
-X ours
and -X theirs
What -X
means is: instead declaring a conflict in this particular case (conflicting diff hunks), simply take either our change (-X ours
) or their change (-X theirs
), discarding the other diff hunk.
A simple example would be where we fixed the spelling of the first word on a line while they fixed the spelling of the fifth word on the same line. Here -X ours
would keep our fix and discard theirs, and -X theirs
would keep their fix and discard ours.
In more complex cases, we might have added or removed some line(s) where they added or removed different line(s), so that the conflicts are harder to think about. Sometimes git diff
can incorrectly synchronize on blank lines or lines consisting of a single close-brace, for instance, resulting in conflicts that something or someone who actually understands the material to be merged, would be able to merge successfully. Again, -X
simply discards either their diff hunk or ours, taking whichever one we told it to.
File conflicts
That's fine as far as it goes, but it only handles diff hunk differences. The conflicts you are seeing are incompatible file changes:
CONFLICT (rename/delete): rcS.d/S08kmod deleted in HEAD and renamed
in local/master. Version local/master of rcS.d/S08kmod left in tree.
In this case, one git diff
(to find "our" changes) found that we (HEAD
) deleted rcS.d/S08kmod
entirely, while they (local/master
) renamed the file. In any delete-file-vs-rename-file conflict, Git keeps the file under the new name, since it's much easier for us to then delete it (git rm newname
) than it is for us to figure out what the new name was and retrieve the ours-or-theirs version of the file.
Auto-merging php5/cli/conf.d/20-xdebug.ini
(this one went well, perhaps using -X theirs
to resolve diff hunk conflicts)
CONFLICT (add/add): Merge conflict in php5/cli/conf.d/20-xdebug.ini
Here, the merge base had no file named php5/cli/conf.d/20-xdebug.ini
. Git leaves both versions in the index; we can use git checkout --ours
to put ours in the work-tree, and git checkout --theirs
to put theirs in the work tree. We still have to merge and resolve this file manually, unfortunately. I have not checked on what Git version 2.8 does here (add/add conflict resolution has been getting some work done on it recently).
Auto-merging apt/sources.list
(another automatic merge went well, again perhaps using -X
)
CONFLICT (rename/rename): Rename "apache2/sites-available/default-ssl"->
"apache2/sites-available/default-ssl.conf.conf" in branch "HEAD"
rename "apache2/sites-available/default-ssl"->
"apache2/sites-available/default-ssl.conf" in "local/master"
In this case, the file in the merge base (under the name apache2/sites-available/default-ssl.conf.conf
) appears, from the two diffs, to have been renamed differently in our changes vs their changes. I'm again not sure what, if anything, Git does about changes within the file, though logically, -X theirs
should apply and take "their" diff hunks at any point where ours and theirs conflict. However, Git does not know which final name to use for the file, so it declares a conflict.
After declaring these conflicts, Git stops and makes the user manually resolve the remaining issues as usual. You may git add
or git rm
whichever version(s) of each file you want, and then git commit
the result. Of course, any time you use -X theirs
or -X ours
, it's wise to test the work-tree in some way (by eyeballing diffs, or running manual or automated tests, or whatever) before committing.