Search code examples
gitgit-diff

Why difference between the local and remote repository is not the same as committed?


I have a merge conflict and the first thing I wanted to see is what is the difference between the local and remote versions of the file. For that I have executed the following:

git diff origin:path/to/my/file.py path/to/my/file.py

This way I saw that my local version has some additional blocks.

Then I wanted to see what exactly did I commit. So I have executed:

git diff --staged path/to/my/file.py

Surprisingly to me I saw another block of changes that is not the same as the block returned by the previous command.

I have opened my file in a text editors and saw both blocks. Now I do not know how to interpret it.

It looks like there is a difference between the local and remote versions and this difference is not the one that I have committed.


Solution

  • I'm not sure why you expect those diffs to be the same in the first place.

    Remember that origin:path/to/my/file.py refers to the version of path/to/my/file.py extracted from the commit to which origin resolves, which is generally origin/HEAD, which is generally origin/master. The git diff command will then compare that file, extracted from that commit, to whatever is in your work-tree right now, which—if you're resolving a merge conflict—will generally be the version with conflict markers surrounding the conflict.

    Aside: git diff

    Note that running git diff during a merge conflict likes to try to show combined diffs. But running git diff <commit-specifier>:<path> <path> avoids that. Meanwhile, running git diff --staged <path> just says:

    $ git diff --staged path/to/file
    * Unmerged path path/to/file
    

    (unless you have git added a version of path/to/file from the work-tree, to tell Git that you have correctly merged the file already, in which case it compares the HEAD version to the one you copied into the index by git adding).

    What git merge is doing

    That's not what git merge is looking at and merging. What git merge is looking at is two diffs:

    • One diff is from your merge base, whichever commit that is, to HEAD.
    • The second diff is from your merge base to your other head, whichever commit that is (maybe origin/master, maybe not).

    You can obtain these two diffs by first obtaining the merge base commit hash. Let's say, to simplify things, you ran git merge origin/master and are now looking at a merge conflict on path/to/my/file.py. Your Git has first run:

    git merge-base --all HEAD origin/master
    

    With any luck, this produces just one commit hash ID.1 You can save that in a variable:

    base=$(git merge-base --all HEAD origin/master)
    

    and then use it in the two specific diffs:

    git diff $base HEAD -- path/to/my/file.py           # what git thinks I changed
    git diff $base origin/master -- path/to/my/file.py  # what git thinks they changed
    

    If you want to compare all three files directly, you can extract them into temporaries. This is what git mergetool does, for instance: it extracts the base path/to/my/file.py, the local (HEAD) file, and the other ("remote head") file, from each of these three commits, and then runs your chosen merge tool on all three files.


    If git merge-base --all produces more than one hash ID, your git merge -s recursive did an internal merge of all of those commits, committed the result as a temporary commit, and is using that commit's hash as the merge base.