0

I would like to make pull by accepting all remote (theirs) modifications, without any manual conflict solving. However I still get conflicts without auto solution:

$ git merge -s recursive -X theirs local/master

CONFLICT (rename/delete): rcS.d/S08kmod deleted in HEAD and renamed in local/master. Version local/master of rcS.d/S08kmod left in tree.
Auto-merging php5/cli/conf.d/20-xdebug.ini
CONFLICT (add/add): Merge conflict in php5/cli/conf.d/20-xdebug.ini
Auto-merging apt/sources.list
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"

Using pull:

$ git pull local master
From ssh://192.168.1.101/etc
 * branch            master     -> FETCH_HEAD
warning: Cannot merge binary files: console-setup/cached_UTF-8_del.kmap.gz (HEAD vs. ada82813d27e5bef846ee086d07a87a82cfbb020)
CONFLICT (rename/delete): rcS.d/S08kmod deleted in HEAD and renamed in ada82813d27e5bef846ee086d07a87a82cfbb020. Version ada82813d27e5bef846ee086d07a87a82cfbb020 of rcS.d/S08kmod left in tree.
CONFLICT (add/add): Merge conflict in php5/cli/conf.d/20-xdebug.ini
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 "ada82813d27e5bef846ee086d07a87a82cfbb020"

I also tried:

git merge --allow-unrelated-histories --strategy-option theirs local/master

But same conflicts.

Was using git v2.8.1 on first example. Upgraded to GIT v2.9.2 before 2nd example.

My use case: I follow Debian upgrade changes in /etc with GIT (more precisely with ETCKEEPER). Now after upgrade done some changes, I would like to accept all changes done by Debian. Of course I trust in changes done by Debian upgrade, so I would like to overwrite (merge) all changes done, without any interaction.

Any idea how to solve their modification conflicts automatically?

klor
  • 1,237
  • 4
  • 12
  • 37
  • I guess you have to resolve the conflicts manually. What happens if you just do `git merge local/master`. I am not familliar with the options you use. I am also not sure whether about what `deleted in HEAD` means... Is that a `detached head` state? By the way: You did a `git pull origin master` first? – Christoph Aug 08 '16 at 15:25
  • Maybe try `git merge --abort` first to start with a clean state? – kostix Aug 08 '16 at 16:01
  • Tried suggestions of @Cristoph, but same conflicts. @kostix: Of course I do `git merge --abort` after each merge which shows conflicts. – klor Aug 08 '16 at 16:42

1 Answers1

5

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.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks @torek for the detailed explanation, I learned from it. My case is very simple. I follow a Debian upgrade changes done in /etc with GIT (more precisely with ETCKEEPER). Now after upgrade done some changes, I would like to accept all changes done by Debian. Of course I trust in changes done by Debian upgrade, so I would like to overwrite (merge) all changes done, without any interaction. That's why I wanted to use -X theirs option, to overwrite my earlier changes. – klor Aug 09 '16 at 15:48