Search code examples
gitgit-mergemerge-conflict-resolution

What exactly `git diff` shows after resolving merge conflict? and how to see only the new changes?


I merge branch A to branch B:

git checkout A
git merge B

a conflict happens, in a file c.txt. I resolve it by editing c.txt file. Now, before approving the changes with git add c.txt I would like to see them. I.e. to see the difference between c.txt and branch A. How to do it?

If I simply use git diff c.txt it shows much more than the changes I want. It seems to show both, changes from branch B and changes from branch A (comparing to their common original commit). I.e. result is different from git add c.txt; git diff --cached c.txt. What exactly does git diff c.txt shows me when c.txt is marked as in conflict?


Solution

  • TL;DR: you probably want git diff HEAD:c.txt c.txt.


    While the file is marked "conflicted", there are multiple "higher stage" entries for the file in Git's index. In fact, this is what it means for the file to be conflicted in the first place: a non-conflicted file is in "stage zero" in Git's index, and a conflicted file has stage 1, 2, and/or 3 entries (typically all three).

    When the file is in this conflicted state, git diff performs a combined diff on the file.

    Using git add on the file erases the higher-stage entries, leaving you with a single stage-zero (non-conflicted) file, which is why it stops showing up that way.

    Now, before approving the changes with git add c.txt I would like to see them. I.e. to see the difference between c.txt and branch A. How to do it?

    I don't normally bother with this (I just read the combined diff, or git add the file and then can see what I want without anything fancier—remember, you can still access the other versions of the file; only the merge base version is hard-ish to find), but you can do this. You must pick out which two copies of c.txt you want compared:

    • One of those is, you say, the copy in some commit (branch A): that's A:c.txt.
    • The other copy is up to you: there's one in the HEAD commit, one in the MERGE_HEAD commit, three in Git's index, and one in your working tree. The tricky part is naming each of these, but we can use the gitrevisions syntax for most of this. As with A:c.txt, HEAD:c.txt and MERGE_HEAD:c.txt work for selecting those versions; :1:c.txt selects the merge base version; :2:c.txt selects the --ours version (copied from HEAD so should match HEAD:c.txt), and :3:c.txt selects the --theirs version (copied from MERGE_HEAD).

    Thus, if you wanted, e.g., to see how HEAD:c.txt differs from MERGE_HEAD:c.txt, you can just run git diff HEAD:c.txt MERGE_HEAD:c.txt (or git diff :2:c.txt :3:c.txt).

    That doesn't help with the one you probably care about the most, though. What about the copy of c.txt in your working tree, as edited to produce the combined file? Fortunately, we can name that one with just the file name, c.txt.

    In some cases, this file name might resemble a branch name, tricking Git. If that's the case, make sure you remember that -- always tells Git: after this, everything is a file name, or at least, not an option and probably not a branch name. So if the file isn't named c.txt but rather main, and:

    git diff HEAD:main main
    

    does the wrong thing, use:

    git diff HEAD:main -- main
    

    (note that HEAD:main isn't going to be mistaken for a branch name and the -- should go between these).

    Note on order reversal problems

    If you wanted to compare the in-tree c.txt to the branch-A c.txt, both:

    git diff c.txt A:c.txt
    

    and:

    git diff A:c.txt c.txt
    

    work. The order of the two arguments determines which file is the "before" or left-side one, and which one is the "after" or right-side one, which in turn determines which lines are deleted and which lines are added.

    In most cases, if the diff you asked for is "backwards" from the order you meant, you can simply swap the arguments. But for:

    git diff HEAD:main -- main
    

    there is a problem: you can't swap these. Here, git diff -R comes to the rescue: the -R flag means swap the sides. So git diff -R HEAD:main -- main does the trick: the main file winds up on the left, and the HEAD:main file winds up on the right.