gitmergegit-configmergetoolp4merge

Why does git mergetool show no conflicts, even though there are conflict markers present in files after a merge?


I’m checked out on feature/my-branch and running git merge dev. The conflict markers added to the file are:

<<<<<<< HEAD
    let foo = "foo"
    let bar = "bar"
||||||| merged common ancestors
    let baz = "baz"
    let bar = "bar"
=======
    let baz = "baz"
    let qux = "qux"
>>>>>>> dev

I then run git mergetool. I have p4mergetool set as my mergetool and it seems to be working. My .gitconfig:

[merge]
    tool = p4mergetool
    conflictstyle = diff3
[mergetool "p4mergetool"]
    cmd = /Applications/p4merge.app/Contents/Resources/launchp4merge $PWD/$BASE $PWD/$REMOTE $PWD/$LOCAL $PWD/$MERGED
    trustExitCode = true

The git mergetool auto resolves the above conflict (0 conflicts shown in the tool) as:

let foo = "foo"
let qux = "qux"

This sort of makes sense: even though HEAD and dev are in conflict, we can see that one branch updated one line and the other branch updated the other line. So we can probably assume what we want.

My questions are:

  1. Is there a way to run/configure git-mergetool or p4mergetool specifically to NOT make this assumption and still show a conflict?
  2. Do I need to run both commands:

    git merge dev
    git mergetool
    

    to have this conflict solved this automatically? I.e. produce the output:

    let foo = "foo"
    let qux = "qux"
    

Said another way: is there a git merge strategy/arguments that I can use to simply run the merge command to produce:

let foo = "foo"
let qux = "qux"

Solution

  • This sort of makes sense: even though HEAD and dev are in conflict, we can see that one branch updated one line and the other branch updated the other line. So we can probably assume what we want.

    That's exactly right.

    1. Is there a way to run/configure git-mergetool or p4mergetool specifically to NOT make this assumption and still show a conflict?

    git mergetool does not make the assumption, so we can conclude that p4mergetool must be the one doing it. I don't have p4mergetool, though, so I don't know if it has a configuration knob to change this.

    1. Do I need to run both commands ...

    Yes: Git's own merge is kind of dumb and merely notices that, gosh, this range of changes on the left abuts (touches) or overlaps this other range of changes on the right, so let's call it a conflict and make the user handle it. In this case, the left-side change (from baz to foo) came just before the right-side change (bar to qux), so the two ranges touched at the edges and Git itself declared a conflict. Had there been one line in between, git merge would have combined the changes on its own, without calling it a conflict.

    (Note that using -X ours or -X theirs would resolve the conflict by discarding one side's change and using only the other's. Again you'd never even get to the point of having p4mergetool do its own thing.)

    What git mergetool does is extract the three files—merge base, left or local or --ours, and right or remote or --theirs versions of some file—and run some other, non-Git program on the three files. When that program finishes, git mergetool can either trust its exit code to decide whether the tool itself merged the files correctly, or run some file comparisons, or just ask you directly: is the work-tree copy of the file now the correct merged copy?

    If the work-tree copy is now the correct final result (or at least git mergetool believes this), git mergetool runs git add, which marks the conflict as being resolved.1 Otherwise, it leaves the conflict in place.

    (I tend to just resolve merge conflicts in my editor.)


    1A file is unmerged if there are any copies of it in any nonzero index slots. A file is merged if there is one copy of it, in slot zero—the normal slot number. Except using some of Git's low-level diagnostic-ish commands (git ls-index --stage, really), you can't actually see these staging slot numbers, but git status calls the file unmerged and tells you whether it is in all three slots (UU) or in just the left or right one (UD and DU respectively). I'm not sure off hand what git status says about a file in slot 1, which would represent the merge base copy of a file with a rename/rename conflict.

    Normally we just use git add or perhaps git rm to overwrite the higher-numbered slots. In the rare case of wanting to restore a resolved file to conflicted state, git checkout -m can do that.