Search code examples
gitdiffparentnotation

Why is the diff reversed for a merge commit with ^! parent notation


Why does the the ^! reverse the diff for merge commit?

From Other ^ Parent Shorthand Notations

The r1^! notation includes commit r1 but excludes all of its parents. By itself, this notation denotes the single commit r1.

This correctly shows me the diff for a non-merge commit.

$ git diff <commit>^!

But for a merge commit, the diff is reversed.

$ git diff <merge-commit>^!

I get similar results for difftool.

Why so?


Solution

  • The short answer is that it is a bug.

    You can see what git diff effectively sees by using git rev-parse. Here's the ^! suffix applied to an ordinary commit:

    $ git rev-parse 699d47e1d^!
    699d47e1d2777ad1c2a867671e35daa821769f29
    ^4aaf5b0b21ac1fc294066593ac5243b3eaff897b
    

    and here it is applied to a merge commit:

    $ git rev-parse 117ddefdb^!
    117ddefdb4dfd9b40ae60967a7327754d8ce7a87
    ^5e5a7cd9327cdbe6b50b5a0ead9b2ee5fb30789c
    ^699d47e1d2777ad1c2a867671e35daa821769f29
    

    When you supply the compressed notation to git diff, Git passes it through the same expansion code that git rev-parse uses. Instead of printing it out, though, git diff then tries to back-interpret the result. If there are two commit hashes (with various flags) it runs a diff between the two named commits. If there are three or more, it does something different.

    Related (but not quite the same): What is the difference between `git diff topic1 topic2 ^master` and `git diff topic1..topic2 ^master`?