Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"-X theirs" option does not seem to work with certain Git conflicts

Tags:

This is an annoying one.

I use this command to merge two branches

git checkout -b temp public/master
git merge -Xtheirs --squash -m "squashed with devtemp" devtemp

I get some conflicts:

CONFLICT (rename/delete): public/github.css deleted in devtemp and renamed in HEAD. Version HEAD of public/github.css left in tree.
Removing public/extra-html/exit-codes.html
Removing public/.gitkeep
Auto-merging package.json
Squash commit -- not updating HEAD
Automatic merge failed; fix conflicts and then commit the result.

How can ignore this type of conflict? I though --theirs/--ours (or whatever the right Git API for those commands is) would solve this type of problem? Is there a better way that actually works?

like image 756
Alexander Mills Avatar asked Oct 26 '16 19:10

Alexander Mills


1 Answers

The -X theirs strategy-argument only directs merge's conflict resolution for individual conflicts within one file, not for high-level full-file conflicts.

In this case, as the message says, the conflict is that "they" (devtemp) removed public/github.css entirely, while "you" (HEAD) renamed it. Your Git is not sure what to do here and has left the file in place under its new name.

If you wish to do what they did, simply git rm the file under its new name, so that it is removed. But ... note that renames are detected by content similarity, so it's possible that they did not actually delete that file at all, and instead deleted another file whose content (before being deleted) was pretty similar to a new file you created. In this case, removing the file is probably wrong.

In all cases, whenever doing a merge, it's wise to test the result before committing. This is especially true when using -X with either ours or theirs. Git has no idea what it is combining and merely relies on textual similarity, and can make nonsensical merges, especially when blindly favoring one side in a case of conflict but using both sides' changes where those changes do not conflict (this is what the -X options do).

Since you were running git merge --squash, which for some reason suppresses the final git commit anyway (a la git merge --no-commit), you will already be expecting to have to run git commit separately. Just resolve the conflict (and test thoroughly, as always) and commit.

Edit (per comments): if you want to do this programmatically—though I would advise against this—you can use git ls-files --stage (perhaps with -z to get better machine-readable output). Any nonzero stage entries imply that the merge failed. The contents of the index will tell you what happened:

  • base version exists, ours exists, theirs does not: a rename/delete conflict in which Git kept "our" renamed version. Remove ours (git rm -- <path>) to take "their" deletion.
  • base version exists, theirs exists, ours does not: a rename/delete conflict in which Git where we deleted the file but they renamed it. The work-tree probably (I have not tested this) has the file under their name. Add it (git add -- <path>) to take their rename. (You will want to set up a test case and verify that this is the right path.)
  • base version does not exist, theirs exists, ours exists: a create/create conflict. Git no doubt keeps our version of the file in the tree. Use git checkout --theirs -- <path> to extract their version to the work-tree, then git add -- <path> to switch to their version.

That should, I think, cover all the unresolved cases. Or, if you have a situation where there are never any actual renames, you are getting false hits from Git's rename detection code, and you will want -X no-renames to disable it.

like image 123
torek Avatar answered Sep 26 '22 16:09

torek