Subject: Re: Merge commit diff results are confusing and inconsistent

From: Ævar Arnfjörð Bjarmason <[email-masked]>
Date: Tue, 07 May 2019 01:52:57 +0200

On Mon, May 06 2019, Robert Dailey wrote:

> I feel like you got hung up too much on exact wording of what I was
> trying to describe. I do apologize I don't have the background to
> explain things 100% accurately, especially at a low level. My
> explanations are mostly intended to be as a user, based on what is
> observable, and based on intent. I'll clarify in the quotes below...
>
> On Fri, May 3, 2019 at 2:12 PM Eckhard Maaß
> <[email-masked]> wrote:
>> Hold on. Basically, there is no such thing as "committed directly" for a
>> merge. You only have differences of the commit to its parents. What you
>> aim for are changes that you cannot find in either preimage - and this
>> can be observed best with the --cc option. Maybe also interesting would
>> be -c for showing a comined diff and -m for showing diffs to parents
>> after one another.
>
> "Committed directly" here means that I made some changes, none of
> which is part of a parent commit. Since no additional commits were
> made following the merge, I assume that within the merge commit is
> some type of diff. If I perform a merge, make some changes, and amend
> those changes into the merge, in mind they ARE contained in that merge
> commit. The underlying machinery doesn't matter here: This is the
> observable state to the user.
>
> Maybe the machinery, which I have no knowledge of or transparency
> into, is important because it is affecting the behavior I'm seeing
> when I do the diffs? Not sure...
>
>> There shouldn't be "just the diff of <commit>" - you always have to tell
>> where to diff it too, intrinsically Git does not save patches, but the
>> whole content, after all.
>
> I do understand this. But again, I'm not trying to be super technical
> here. In plain english, all I'm trying to say is that I want to see
> the changes that 1 commit introduces into the code base. So when it
> comes to communicating the end result I want, I talk about it in terms
> of 1 commit (the merge commit). The means to get that output is part
> of my question and overall confusion. But as a baseline, I want to
> clarify that I do understand a range is required input for the diff
> command. In the case of merge commits, the way you specify the ranges
> has many forms so I'm not sure based on the results I see, which one
> is correct or what they all mean.
>
>> Somebody else might know better why the diff actually produced the
>> results you were looking for. I admit it is puzzling to me - I would
>> have expected to error it out on the output of git rev-parse as there
>> are three items.
>
> Actually I can't think of any other command that can show me what
> revision ranges translate to in "raw" commits. To me the raw forms are
> always <sha1> and ^<sha1>, repeated as many times and in as many
> orders necessary. Don't all of the vanity revision specifications
> ultimately boil down to "from this parent" and "not from this parent"?

Maybe an example helps, let's say you have two paint buckets, one with
red paint, one with yellow paint. You mix them. What happens?

    (
        rm -rf /tmp/git &&
        git init /tmp/git &&
        cd /tmp/git &&
        git checkout -b red &&

        echo red >color.txt &&
        git add color.txt &&
        git commit -m"red" &&

        git checkout --orphan green &&
        git reset --hard &&
        echo green >color.txt &&
        git add color.txt &&
        git commit -m"green" &&

        git merge --allow-unrelated-histories red;
        echo yellow >color.txt &&
        git add color.txt &&
        git commit -m"red + green = yellow"
    )

I *think* what you're alluding to is trying to discover some sort of
change to whatever the default merge resolution would have been, which
in this case would be closer to:

    (echo green && echo red) >color.txt

But it's important to understand that the whole business of suggesting
how you should merge is just sugar that isn't in any way represented in
the object model that makes it into the repository.

In that model we just had one branch with "color.txt" containing "red",
and another with "green". Then we merged the two together and that
commit merged two histories together, did something to yield an end
result, and now the "color.txt" file contains "yellow".

But what single thing can you look at to describe how you ended up with
"yellow"? There isn't such a single thing, I just know that I have a
commit with two parents:

    $ git cat-file -p HEAD
    tree 6318a50d67e6de533498a4a0c9f46360cff6908a
    parent 2332fc6b40c1cbf9f5daf809f09eb4defdd2ce30
    parent 1707f13d2d236d61ac7496962ecebc50ffff5be3

And that if I diff against the 1st parent we went from green to yellow:

    $ git diff HEAD^1..HEAD
    diff --git a/color.txt b/color.txt
    index a5b73ed..d1ed081 100644
    --- a/color.txt
    +++ b/color.txt
    @@ -1 +1 @@
    -green
    +yellow

And the other from red to yellow:

    $ git diff HEAD^2..HEAD
    diff --git a/color.txt b/color.txt
    index a9d1386..d1ed081 100644
    --- a/color.txt
    +++ b/color.txt
    @@ -1 +1 @@
    -red
    +yellow

To the extent that we can show a single diff at all that's diff-tree's
--cc option:

    $ git diff-tree --cc HEAD
    e89ef1f780d7c979c18cc0f03fd74c560466ef03
    diff --cc color.txt
    index a5b73ed,a9d1386..d1ed081
    --- a/color.txt
    +++ b/color.txt
    @@@ -1,1 -1,1 +1,1 @@@
    - green
     -red
    ++yellow

Sometimes it makes things better, sometimes it's just more
confusing. It's what "git show" will use to render merge commits.