{"thread":{"id":"51029","subject":"Merge commit diff results are confusing and inconsistent","startedAt":"2019-05-03T15:56:09Z","lastAt":"2019-05-11T14:08:43Z","messageCount":12,"participants":["Robert Dailey","Eckhard Maaß","Ævar Arnfjörð Bjarmason","Denton Liu","Elijah Newren","Philip Oakley"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"374885","messageId":"CAHd499BEHd79zL76um2oB4YMdScM2icrMXstg1g=xwdBqk43EQ@mail.gmail.com","threadId":"51029","inReplyTo":null,"subject":"Merge commit diff results are confusing and inconsistent","fromName":"Robert Dailey","fromEmail":"rcdailey.lists@gmail.com","sentAt":"2019-05-03T15:55:54Z","receivedAt":"2019-05-03T15:56:09Z","isPatch":false,"sender":{"key":"rcdailey.lists@gmail.com","avatar":null},"body":"I'm hoping this is mostly a learning opportunity for me. I'm assuming\nthings are working as designed, but I just don't understand something\nfundamental.\n\nI have a merge commit. HEAD is currently pointing at this merge\ncommit. To be exact, HEAD points to master, which points to the merge\ncommit. My goal is to diff only the changes in the merge commit (stuff\ncommitted directly in the merge commit, such as conflict resolutions).\nTo start out, I learned about @^@, @^!, and @^-. @^! sounded like what\nI wanted. It gives me this output:\n\n$ git rev-parse @^!\n21f5a4b9fee4f12e7793919f65361d2c16f7d240\n^14bd840c1d591c9dc066ed1aab59b5ec14d502bb\n^944af379480826764f2f31b67848e2885b95b4a6\n\nPerfect. This should give me just the diff of 21f5... and exclude\neverything else, right? So I did this:\n\n$ git diff @^!\n\nHowever, I get *all* changes on the branch (second parent) and changes\nin the merge commit itself. Basically it acts as if I used @^-, which\nseems wrong to me. So to test another angle, I used the revisions\noutput by rev-parse directly:\n\n$ git diff 21f5a4b9fee4f12e7793919f65361d2c16f7d240\n^14bd840c1d591c9dc066ed1aab59b5ec14d502bb\n^944af379480826764f2f31b67848e2885b95b4a6\n\nInterestingly, this showed me only the changes in the merge commit\n(21f5a4) and nothing else. Between this command and @^!, I feel the\ntwo are exactly the same. So why does @^! not work as I expect, but\nexplicitly specifying the revisions does? What am I missing here?\n\nWhen I use @^! in `git log`, I do only see the merge commit and no\nother commits. So at least log is treating it correctly.\n\n$ git version\ngit version 2.20.1.windows.1\n"},{"id":"374889","messageId":"20190503191231.GA5426@esm","threadId":"51029","inReplyTo":"CAHd499BEHd79zL76um2oB4YMdScM2icrMXstg1g=xwdBqk43EQ@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Eckhard Maaß","fromEmail":"eckhard.s.maass@googlemail.com","sentAt":"2019-05-03T19:12:31Z","receivedAt":"2019-05-03T19:12:47Z","isPatch":false,"sender":{"key":"eckhard.s.maass@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/21984134?v=4"},"body":"On Fri, May 03, 2019 at 10:55:54AM -0500, Robert Dailey wrote:\n> I have a merge commit. HEAD is currently pointing at this merge\n> commit. To be exact, HEAD points to master, which points to the merge\n> commit. My goal is to diff only the changes in the merge commit (stuff\n> committed directly in the merge commit, such as conflict resolutions).\n\nHold on. Basically, there is no such thing as \"committed directly\" for a\nmerge. You only have differences of the commit to its parents. What you\naim for are changes that you cannot find in either preimage - and this\ncan be observed best with the --cc option. Maybe also interesting would\nbe -c for showing a comined diff and -m for showing diffs to parents\nafter one another.\n\n> To start out, I learned about @^@, @^!, and @^-. @^! sounded like what\n> I wanted. It gives me this output:\n> \n> $ git rev-parse @^!\n> 21f5a4b9fee4f12e7793919f65361d2c16f7d240\n> ^14bd840c1d591c9dc066ed1aab59b5ec14d502bb\n> ^944af379480826764f2f31b67848e2885b95b4a6\n> \n> Perfect. This should give me just the diff of 21f5... and exclude\n> everything else, right? So I did this:\n\nThere shouldn't be \"just the diff of <commit>\" - you always have to tell\nwhere to diff it too, intrinsically Git does not save patches, but the\nwhole content, after all.\n\n> \n> $ git diff @^!\n> \n> However, I get *all* changes on the branch (second parent) and changes\n> in the merge commit itself. Basically it acts as if I used @^-, which\n> seems wrong to me. So to test another angle, I used the revisions\n> output by rev-parse directly:\n> \n> $ git diff 21f5a4b9fee4f12e7793919f65361d2c16f7d240\n> ^14bd840c1d591c9dc066ed1aab59b5ec14d502bb\n> ^944af379480826764f2f31b67848e2885b95b4a6\n> \n> Interestingly, this showed me only the changes in the merge commit\n> (21f5a4) and nothing else. Between this command and @^!, I feel the\n> two are exactly the same. So why does @^! not work as I expect, but\n> explicitly specifying the revisions does? What am I missing here?\n> \n> When I use @^! in `git log`, I do only see the merge commit and no\n> other commits. So at least log is treating it correctly.\n\nSomebody else might know better why the diff actually produced the\nresults you were looking for. I admit it is puzzling to me - I would\nhave expected to error it out on the output of git rev-parse as there\nare three items.\n\nGreetings,\nEckhard\n"},{"id":"374948","messageId":"CAHd499CUOnFVkNGEG-MmG5OsUPpmWHET2X1j1fjNuGUkELf-5w@mail.gmail.com","threadId":"51029","inReplyTo":"20190503191231.GA5426@esm","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Robert Dailey","fromEmail":"rcdailey.lists@gmail.com","sentAt":"2019-05-06T15:38:12Z","receivedAt":"2019-05-06T15:38:27Z","isPatch":false,"sender":{"key":"rcdailey.lists@gmail.com","avatar":null},"body":"I feel like you got hung up too much on exact wording of what I was\ntrying to describe. I do apologize I don't have the background to\nexplain things 100% accurately, especially at a low level. My\nexplanations are mostly intended to be as a user, based on what is\nobservable, and based on intent. I'll clarify in the quotes below...\n\nOn Fri, May 3, 2019 at 2:12 PM Eckhard Maaß\n<eckhard.s.maass@googlemail.com> wrote:\n> Hold on. Basically, there is no such thing as \"committed directly\" for a\n> merge. You only have differences of the commit to its parents. What you\n> aim for are changes that you cannot find in either preimage - and this\n> can be observed best with the --cc option. Maybe also interesting would\n> be -c for showing a comined diff and -m for showing diffs to parents\n> after one another.\n\n\"Committed directly\" here means that I made some changes, none of\nwhich is part of a parent commit. Since no additional commits were\nmade following the merge, I assume that within the merge commit is\nsome type of diff. If I perform a merge, make some changes, and amend\nthose changes into the merge, in mind they ARE contained in that merge\ncommit. The underlying machinery doesn't matter here: This is the\nobservable state to the user.\n\nMaybe the machinery, which I have no knowledge of or transparency\ninto, is important because it is affecting the behavior I'm seeing\nwhen I do the diffs? Not sure...\n\n> There shouldn't be \"just the diff of <commit>\" - you always have to tell\n> where to diff it too, intrinsically Git does not save patches, but the\n> whole content, after all.\n\nI do understand this. But again, I'm not trying to be super technical\nhere. In plain english, all I'm trying to say is that I want to see\nthe changes that 1 commit introduces into the code base. So when it\ncomes to communicating the end result I want, I talk about it in terms\nof 1 commit (the merge commit). The means to get that output is part\nof my question and overall confusion. But as a baseline, I want to\nclarify that I do understand a range is required input for the diff\ncommand. In the case of merge commits, the way you specify the ranges\nhas many forms so I'm not sure based on the results I see, which one\nis correct or what they all mean.\n\n> Somebody else might know better why the diff actually produced the\n> results you were looking for. I admit it is puzzling to me - I would\n> have expected to error it out on the output of git rev-parse as there\n> are three items.\n\nActually I can't think of any other command that can show me what\nrevision ranges translate to in \"raw\" commits. To me the raw forms are\nalways <sha1> and ^<sha1>, repeated as many times and in as many\norders necessary. Don't all of the vanity revision specifications\nultimately boil down to \"from this parent\" and \"not from this parent\"?\n"},{"id":"374949","messageId":"20190506165218.GA21965@esm","threadId":"51029","inReplyTo":"CAHd499CUOnFVkNGEG-MmG5OsUPpmWHET2X1j1fjNuGUkELf-5w@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Eckhard Maaß","fromEmail":"eckhard.s.maass@googlemail.com","sentAt":"2019-05-06T16:52:18Z","receivedAt":"2019-05-06T16:52:25Z","isPatch":false,"sender":{"key":"eckhard.s.maass@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/21984134?v=4"},"body":"On Mon, May 06, 2019 at 10:38:12AM -0500, Robert Dailey wrote:\n> I feel like you got hung up too much on exact wording of what I was\n> trying to describe. I do apologize I don't have the background to\n> explain things 100% accurately, especially at a low level. My\n> explanations are mostly intended to be as a user, based on what is\n> observable, and based on intent. I'll clarify in the quotes below...\n\nI doubt that what is observable is what you described.\n\n> \n> On Fri, May 3, 2019 at 2:12 PM Eckhard Maaß\n> <eckhard.s.maass@googlemail.com> wrote:\n> > Hold on. Basically, there is no such thing as \"committed directly\" for a\n> > merge. You only have differences of the commit to its parents. What you\n> > aim for are changes that you cannot find in either preimage - and this\n> > can be observed best with the --cc option. Maybe also interesting would\n> > be -c for showing a comined diff and -m for showing diffs to parents\n> > after one another.\n> \n> \"Committed directly\" here means that I made some changes, none of\n> which is part of a parent commit.\n\nThe merge strategy does the same when melding two versions of a file\ninto one. If one invents a more clever merge strategy, it might appear\nthat you have \"some changes\". This is not a mere technicality - you\ncannot make a difference between parts that were conflicts in the\noriginal commit and changes you introduced yourself - short of making\nthe merge again.\n\n> Since no additional commits were\n> made following the merge, I assume that within the merge commit is\n> some type of diff. If I perform a merge, make some changes, and amend\n> those changes into the merge, in mind they ARE contained in that merge\n> commit. The underlying machinery doesn't matter here: This is the\n> observable state to the user.\n\nWell, they are contained in the merge commit, true. And they would show\nin the diffs to the two parents.\n\n> Maybe the machinery, which I have no knowledge of or transparency\n> into, is important because it is affecting the behavior I'm seeing\n> when I do the diffs? Not sure...\n> \n> > There shouldn't be \"just the diff of <commit>\" - you always have to tell\n> > where to diff it too, intrinsically Git does not save patches, but the\n> > whole content, after all.\n> \n> I do understand this. But again, I'm not trying to be super technical\n> here. In plain english, all I'm trying to say is that I want to see\n> the changes that 1 commit introduces into the code base.\n\nI do not understand - I especially reiterated on the fundamental design\ndecision of Git here that one cannot speak of *the* change. This is not\njust some small technical detail.\n\nWith a merge commit, there is no such thing as \"*the change* introduced\ninto the code base\". You can view it a few different ways:\n\n- the content of the merge commit as a whole. However, this is not\n  really a change, but the whole content.\n\n- the diff to one of its parents. If you merge a feature branch to\n  master, then git diff master^..master does give you the changes\n  introduced in master by the merge (given that merge^ is the state\n  beforehand)\n\n- all the diffs (or condensed forms) to all parent commits. --cc helps\n  you here by ignoring \"uninteresting\" hunks.\n\n> So when it\n> comes to communicating the end result I want, I talk about it in terms\n> of 1 commit (the merge commit). The means to get that output is part\n> of my question and overall confusion. But as a baseline, I want to\n> clarify that I do understand a range is required input for the diff\n> command. In the case of merge commits, the way you specify the ranges\n> has many forms so I'm not sure based on the results I see, which one\n> is correct or what they all mean.\n\nI doubt that the intentions of the revision short hands you gave have\nshould have some meaningful transition to the diff machinery. For me,\nhere some technicality strikes and gives results which are\ncounterintuitive to me - for me, all your calls should result in errors.\n\n> \n> > Somebody else might know better why the diff actually produced the\n> > results you were looking for. I admit it is puzzling to me - I would\n> > have expected to error it out on the output of git rev-parse as there\n> > are three items.\n> \n> Actually I can't think of any other command that can show me what\n> revision ranges translate to in \"raw\" commits. To me the raw forms are\n> always <sha1> and ^<sha1>, repeated as many times and in as many\n> orders necessary. Don't all of the vanity revision specifications\n> ultimately boil down to \"from this parent\" and \"not from this parent\"?\n\nThis seems to be very wrong for calculating a diff - you nee exactly to\npoints to compare. So it is always a \"from one\" and \"to one\".\n\nGreetings,\nEckhard\n"},{"id":"374969","messageId":"874l67i1ie.fsf@evledraar.gmail.com","threadId":"51029","inReplyTo":"CAHd499CUOnFVkNGEG-MmG5OsUPpmWHET2X1j1fjNuGUkELf-5w@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-06T23:52:57Z","receivedAt":"2019-05-06T23:53:02Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, May 06 2019, Robert Dailey wrote:\n\n> I feel like you got hung up too much on exact wording of what I was\n> trying to describe. I do apologize I don't have the background to\n> explain things 100% accurately, especially at a low level. My\n> explanations are mostly intended to be as a user, based on what is\n> observable, and based on intent. I'll clarify in the quotes below...\n>\n> On Fri, May 3, 2019 at 2:12 PM Eckhard Maaß\n> <eckhard.s.maass@googlemail.com> wrote:\n>> Hold on. Basically, there is no such thing as \"committed directly\" for a\n>> merge. You only have differences of the commit to its parents. What you\n>> aim for are changes that you cannot find in either preimage - and this\n>> can be observed best with the --cc option. Maybe also interesting would\n>> be -c for showing a comined diff and -m for showing diffs to parents\n>> after one another.\n>\n> \"Committed directly\" here means that I made some changes, none of\n> which is part of a parent commit. Since no additional commits were\n> made following the merge, I assume that within the merge commit is\n> some type of diff. If I perform a merge, make some changes, and amend\n> those changes into the merge, in mind they ARE contained in that merge\n> commit. The underlying machinery doesn't matter here: This is the\n> observable state to the user.\n>\n> Maybe the machinery, which I have no knowledge of or transparency\n> into, is important because it is affecting the behavior I'm seeing\n> when I do the diffs? Not sure...\n>\n>> There shouldn't be \"just the diff of <commit>\" - you always have to tell\n>> where to diff it too, intrinsically Git does not save patches, but the\n>> whole content, after all.\n>\n> I do understand this. But again, I'm not trying to be super technical\n> here. In plain english, all I'm trying to say is that I want to see\n> the changes that 1 commit introduces into the code base. So when it\n> comes to communicating the end result I want, I talk about it in terms\n> of 1 commit (the merge commit). The means to get that output is part\n> of my question and overall confusion. But as a baseline, I want to\n> clarify that I do understand a range is required input for the diff\n> command. In the case of merge commits, the way you specify the ranges\n> has many forms so I'm not sure based on the results I see, which one\n> is correct or what they all mean.\n>\n>> Somebody else might know better why the diff actually produced the\n>> results you were looking for. I admit it is puzzling to me - I would\n>> have expected to error it out on the output of git rev-parse as there\n>> are three items.\n>\n> Actually I can't think of any other command that can show me what\n> revision ranges translate to in \"raw\" commits. To me the raw forms are\n> always <sha1> and ^<sha1>, repeated as many times and in as many\n> orders necessary. Don't all of the vanity revision specifications\n> ultimately boil down to \"from this parent\" and \"not from this parent\"?\n\nMaybe an example helps, let's say you have two paint buckets, one with\nred paint, one with yellow paint. You mix them. What happens?\n\n    (\n        rm -rf /tmp/git &&\n        git init /tmp/git &&\n        cd /tmp/git &&\n        git checkout -b red &&\n\n        echo red >color.txt &&\n        git add color.txt &&\n        git commit -m\"red\" &&\n\n        git checkout --orphan green &&\n        git reset --hard &&\n        echo green >color.txt &&\n        git add color.txt &&\n        git commit -m\"green\" &&\n\n        git merge --allow-unrelated-histories red;\n        echo yellow >color.txt &&\n        git add color.txt &&\n        git commit -m\"red + green = yellow\"\n    )\n\nI *think* what you're alluding to is trying to discover some sort of\nchange to whatever the default merge resolution would have been, which\nin this case would be closer to:\n\n    (echo green && echo red) >color.txt\n\nBut it's important to understand that the whole business of suggesting\nhow you should merge is just sugar that isn't in any way represented in\nthe object model that makes it into the repository.\n\nIn that model we just had one branch with \"color.txt\" containing \"red\",\nand another with \"green\". Then we merged the two together and that\ncommit merged two histories together, did something to yield an end\nresult, and now the \"color.txt\" file contains \"yellow\".\n\nBut what single thing can you look at to describe how you ended up with\n\"yellow\"? There isn't such a single thing, I just know that I have a\ncommit with two parents:\n\n    $ git cat-file -p HEAD\n    tree 6318a50d67e6de533498a4a0c9f46360cff6908a\n    parent 2332fc6b40c1cbf9f5daf809f09eb4defdd2ce30\n    parent 1707f13d2d236d61ac7496962ecebc50ffff5be3\n\nAnd that if I diff against the 1st parent we went from green to yellow:\n\n    $ git diff HEAD^1..HEAD\n    diff --git a/color.txt b/color.txt\n    index a5b73ed..d1ed081 100644\n    --- a/color.txt\n    +++ b/color.txt\n    @@ -1 +1 @@\n    -green\n    +yellow\n\nAnd the other from red to yellow:\n\n    $ git diff HEAD^2..HEAD\n    diff --git a/color.txt b/color.txt\n    index a9d1386..d1ed081 100644\n    --- a/color.txt\n    +++ b/color.txt\n    @@ -1 +1 @@\n    -red\n    +yellow\n\nTo the extent that we can show a single diff at all that's diff-tree's\n--cc option:\n\n    $ git diff-tree --cc HEAD\n    e89ef1f780d7c979c18cc0f03fd74c560466ef03\n    diff --cc color.txt\n    index a5b73ed,a9d1386..d1ed081\n    --- a/color.txt\n    +++ b/color.txt\n    @@@ -1,1 -1,1 +1,1 @@@\n    - green\n     -red\n    ++yellow\n\nSometimes it makes things better, sometimes it's just more\nconfusing. It's what \"git show\" will use to render merge commits.\n"},{"id":"375031","messageId":"CAHd499BkdpsA2BdB0Hsv3xXzpMyMzW8CSuYf2gQX0Jf7OoYBGw@mail.gmail.com","threadId":"51029","inReplyTo":"874l67i1ie.fsf@evledraar.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Robert Dailey","fromEmail":"rcdailey.lists@gmail.com","sentAt":"2019-05-07T14:10:12Z","receivedAt":"2019-05-07T14:10:29Z","isPatch":false,"sender":{"key":"rcdailey.lists@gmail.com","avatar":null},"body":"On Mon, May 6, 2019 at 6:52 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> Maybe an example helps, let's say you have two paint buckets, one with\n> red paint, one with yellow paint. You mix them. What happens?\n>\n>     (\n>         rm -rf /tmp/git &&\n>         git init /tmp/git &&\n>         cd /tmp/git &&\n>         git checkout -b red &&\n>\n>         echo red >color.txt &&\n>         git add color.txt &&\n>         git commit -m\"red\" &&\n>\n>         git checkout --orphan green &&\n>         git reset --hard &&\n>         echo green >color.txt &&\n>         git add color.txt &&\n>         git commit -m\"green\" &&\n>\n>         git merge --allow-unrelated-histories red;\n>         echo yellow >color.txt &&\n>         git add color.txt &&\n>         git commit -m\"red + green = yellow\"\n>     )\n>\n> I *think* what you're alluding to is trying to discover some sort of\n> change to whatever the default merge resolution would have been, which\n> in this case would be closer to:\n>\n>     (echo green && echo red) >color.txt\n>\n> But it's important to understand that the whole business of suggesting\n> how you should merge is just sugar that isn't in any way represented in\n> the object model that makes it into the repository.\n>\n> In that model we just had one branch with \"color.txt\" containing \"red\",\n> and another with \"green\". Then we merged the two together and that\n> commit merged two histories together, did something to yield an end\n> result, and now the \"color.txt\" file contains \"yellow\".\n>\n> But what single thing can you look at to describe how you ended up with\n> \"yellow\"? There isn't such a single thing, I just know that I have a\n> commit with two parents:\n>\n>     $ git cat-file -p HEAD\n>     tree 6318a50d67e6de533498a4a0c9f46360cff6908a\n>     parent 2332fc6b40c1cbf9f5daf809f09eb4defdd2ce30\n>     parent 1707f13d2d236d61ac7496962ecebc50ffff5be3\n>\n> And that if I diff against the 1st parent we went from green to yellow:\n>\n>     $ git diff HEAD^1..HEAD\n>     diff --git a/color.txt b/color.txt\n>     index a5b73ed..d1ed081 100644\n>     --- a/color.txt\n>     +++ b/color.txt\n>     @@ -1 +1 @@\n>     -green\n>     +yellow\n>\n> And the other from red to yellow:\n>\n>     $ git diff HEAD^2..HEAD\n>     diff --git a/color.txt b/color.txt\n>     index a9d1386..d1ed081 100644\n>     --- a/color.txt\n>     +++ b/color.txt\n>     @@ -1 +1 @@\n>     -red\n>     +yellow\n>\n> To the extent that we can show a single diff at all that's diff-tree's\n> --cc option:\n>\n>     $ git diff-tree --cc HEAD\n>     e89ef1f780d7c979c18cc0f03fd74c560466ef03\n>     diff --cc color.txt\n>     index a5b73ed,a9d1386..d1ed081\n>     --- a/color.txt\n>     +++ b/color.txt\n>     @@@ -1,1 -1,1 +1,1 @@@\n>     - green\n>      -red\n>     ++yellow\n>\n> Sometimes it makes things better, sometimes it's just more\n> confusing. It's what \"git show\" will use to render merge commits.\n\nYour example is very helpful. I understand what you're saying for\nconflicted lines. But the \"whatever the default merge resolution would\nhave been\" doesn't exist, because there's no reality where line 1 in\ncolor.txt can be something \"automatic\" (i.e. deduced by git). The only\nreality for the merge commit is some hand-edited replacement to line\n1. So there is no \"diff what I see with some alternate reality\".\n\nThe majority use case I'm interested in is seeing net-positive changes\nthat happen in merge commits. Normally I take for granted that merge\ncommits have nothing meaningful in them (meaningful here defined as\nsomething unexpected for a merge commit). But what if someone makes a\npoor decision and does some crazy refactoring in 1 file and amends it\ninto a merge commit? Let's also say that these changes are done to a\nfile that wasn't modified in any parent (say a unrelated.txt next to\nyour color.txt). Since neither parent cares about that file for the\npurposes of the merge, I am trying to make sense of a revision\nspecification that can be used to see what they did to that file.\n\nEven ignoring that issue, the more concerning observation of mine is\nthat `diff @^!` produces any output at all. If you exclude both\nparents, why do I see a diff for parent 2 (I see the complete diff of\nthe branch that was merged in)?\n\nAgain, thank you for your example, you definitely made things very\nclear for me. I see where the confusion is. And I think --cc is a good\nway to get more context. At this point I'm just concerned about the\n@^! behavior with merge commits & diff.\n"},{"id":"375034","messageId":"CAHd499DvwcHGkSF6DjivvceUaMRO994Q8e1JrjNqjzpQkhDMFg@mail.gmail.com","threadId":"51029","inReplyTo":"CAHd499BkdpsA2BdB0Hsv3xXzpMyMzW8CSuYf2gQX0Jf7OoYBGw@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Robert Dailey","fromEmail":"rcdailey.lists@gmail.com","sentAt":"2019-05-07T14:41:00Z","receivedAt":"2019-05-07T14:41:15Z","isPatch":false,"sender":{"key":"rcdailey.lists@gmail.com","avatar":null},"body":"On Tue, May 7, 2019 at 9:10 AM Robert Dailey <rcdailey.lists@gmail.com> wrote:\n> Your example is very helpful. I understand what you're saying for\n> conflicted lines. But the \"whatever the default merge resolution would\n> have been\" doesn't exist, because there's no reality where line 1 in\n> color.txt can be something \"automatic\" (i.e. deduced by git). The only\n> reality for the merge commit is some hand-edited replacement to line\n> 1. So there is no \"diff what I see with some alternate reality\".\n>\n> The majority use case I'm interested in is seeing net-positive changes\n> that happen in merge commits. Normally I take for granted that merge\n> commits have nothing meaningful in them (meaningful here defined as\n> something unexpected for a merge commit). But what if someone makes a\n> poor decision and does some crazy refactoring in 1 file and amends it\n> into a merge commit? Let's also say that these changes are done to a\n> file that wasn't modified in any parent (say a unrelated.txt next to\n> your color.txt). Since neither parent cares about that file for the\n> purposes of the merge, I am trying to make sense of a revision\n> specification that can be used to see what they did to that file.\n>\n> Even ignoring that issue, the more concerning observation of mine is\n> that `diff @^!` produces any output at all. If you exclude both\n> parents, why do I see a diff for parent 2 (I see the complete diff of\n> the branch that was merged in)?\n>\n> Again, thank you for your example, you definitely made things very\n> clear for me. I see where the confusion is. And I think --cc is a good\n> way to get more context. At this point I'm just concerned about the\n> @^! behavior with merge commits & diff.\n\nAlso I'm really confused how you got diff-tree to work. If I pick any\narbitrary SHA1 of a merge commit in my existing repo's history,\ndiff-tree produces only a SHA1 as the result:\n\n$ git diff-tree --cc bdd47a73d\nbdd47a73d18948aa46a8a7aa964543f0d989ffd4\n\nI tried with just `-c` as well; same result.\n"},{"id":"375036","messageId":"20190507145849.GA6313@archbookpro.localdomain","threadId":"51029","inReplyTo":"CAHd499BkdpsA2BdB0Hsv3xXzpMyMzW8CSuYf2gQX0Jf7OoYBGw@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-05-07T14:58:49Z","receivedAt":"2019-05-07T14:58:54Z","isPatch":false,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Robert,\n\nOn Tue, May 07, 2019 at 09:10:12AM -0500, Robert Dailey wrote:\n\n[snip]\n\n> Even ignoring that issue, the more concerning observation of mine is\n> that `diff @^!` produces any output at all. If you exclude both\n> parents, why do I see a diff for parent 2 (I see the complete diff of\n> the branch that was merged in)?\n> \n> Again, thank you for your example, you definitely made things very\n> clear for me. I see where the confusion is. And I think --cc is a good\n> way to get more context. At this point I'm just concerned about the\n> @^! behavior with merge commits & diff.\n\n@^! is undocumented behaviour. Junio touched on why it behaves this way\nhere[1], in case you're interested.\n\nFor more details, this code[2] just blindly diffs the first two\nendpoints returned preceding `repo_init_revisions`.\n\nAlso, not to rehash an old discussion but I'll let this thread be my\nargument *against* allowing range-notation in git-diff.\n\n[1]: https://public-inbox.org/git/xmqqef7ch80v.fsf@gitster-ct.c.googlers.com/\n[2]: https://github.com/gitster/git/blob/master/builtin/diff.c#L385\n"},{"id":"375041","messageId":"87woj2guam.fsf@evledraar.gmail.com","threadId":"51029","inReplyTo":"CAHd499BkdpsA2BdB0Hsv3xXzpMyMzW8CSuYf2gQX0Jf7OoYBGw@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-07T15:26:25Z","receivedAt":"2019-05-07T15:26:32Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, May 07 2019, Robert Dailey wrote:\n\n> On Mon, May 6, 2019 at 6:52 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>> Maybe an example helps, let's say you have two paint buckets, one with\n>> red paint, one with yellow paint. You mix them. What happens?\n>>\n>>     (\n>>         rm -rf /tmp/git &&\n>>         git init /tmp/git &&\n>>         cd /tmp/git &&\n>>         git checkout -b red &&\n>>\n>>         echo red >color.txt &&\n>>         git add color.txt &&\n>>         git commit -m\"red\" &&\n>>\n>>         git checkout --orphan green &&\n>>         git reset --hard &&\n>>         echo green >color.txt &&\n>>         git add color.txt &&\n>>         git commit -m\"green\" &&\n>>\n>>         git merge --allow-unrelated-histories red;\n>>         echo yellow >color.txt &&\n>>         git add color.txt &&\n>>         git commit -m\"red + green = yellow\"\n>>     )\n>>\n>> I *think* what you're alluding to is trying to discover some sort of\n>> change to whatever the default merge resolution would have been, which\n>> in this case would be closer to:\n>>\n>>     (echo green && echo red) >color.txt\n>>\n>> But it's important to understand that the whole business of suggesting\n>> how you should merge is just sugar that isn't in any way represented in\n>> the object model that makes it into the repository.\n>>\n>> In that model we just had one branch with \"color.txt\" containing \"red\",\n>> and another with \"green\". Then we merged the two together and that\n>> commit merged two histories together, did something to yield an end\n>> result, and now the \"color.txt\" file contains \"yellow\".\n>>\n>> But what single thing can you look at to describe how you ended up with\n>> \"yellow\"? There isn't such a single thing, I just know that I have a\n>> commit with two parents:\n>>\n>>     $ git cat-file -p HEAD\n>>     tree 6318a50d67e6de533498a4a0c9f46360cff6908a\n>>     parent 2332fc6b40c1cbf9f5daf809f09eb4defdd2ce30\n>>     parent 1707f13d2d236d61ac7496962ecebc50ffff5be3\n>>\n>> And that if I diff against the 1st parent we went from green to yellow:\n>>\n>>     $ git diff HEAD^1..HEAD\n>>     diff --git a/color.txt b/color.txt\n>>     index a5b73ed..d1ed081 100644\n>>     --- a/color.txt\n>>     +++ b/color.txt\n>>     @@ -1 +1 @@\n>>     -green\n>>     +yellow\n>>\n>> And the other from red to yellow:\n>>\n>>     $ git diff HEAD^2..HEAD\n>>     diff --git a/color.txt b/color.txt\n>>     index a9d1386..d1ed081 100644\n>>     --- a/color.txt\n>>     +++ b/color.txt\n>>     @@ -1 +1 @@\n>>     -red\n>>     +yellow\n>>\n>> To the extent that we can show a single diff at all that's diff-tree's\n>> --cc option:\n>>\n>>     $ git diff-tree --cc HEAD\n>>     e89ef1f780d7c979c18cc0f03fd74c560466ef03\n>>     diff --cc color.txt\n>>     index a5b73ed,a9d1386..d1ed081\n>>     --- a/color.txt\n>>     +++ b/color.txt\n>>     @@@ -1,1 -1,1 +1,1 @@@\n>>     - green\n>>      -red\n>>     ++yellow\n>>\n>> Sometimes it makes things better, sometimes it's just more\n>> confusing. It's what \"git show\" will use to render merge commits.\n\nFirst, to update my example this is bette (I was being overly clever\nwith the unrelated histories):\n\n    (\n        rm -rf /tmp/git &&\n        git init /tmp/git &&\n        cd /tmp/git &&\n\n\t>color.txt &&\n        git add color.txt &&\n        git commit -m\"base version\" &&\n        git tag base &&\n\n        git checkout -b red base &&\n        echo red >color.txt &&\n        git add color.txt &&\n        git commit -m\"red\" &&\n\n        git checkout -b green base &&\n        echo green >color.txt &&\n        git add color.txt &&\n        git commit -m\"green\" &&\n\n        git checkout master &&\n        git merge red green;\n        echo yellow >color.txt &&\n        git add color.txt &&\n        echo hello >README.txt &&\n        git add README.txt &&\n        git commit -m\"red + green = yellow\"\n    )\n\n\n> Your example is very helpful. I understand what you're saying for\n> conflicted lines. But the \"whatever the default merge resolution would\n> have been\" doesn't exist, because there's no reality where line 1 in\n> color.txt can be something \"automatic\" (i.e. deduced by git). The only\n> reality for the merge commit is some hand-edited replacement to line\n> 1. So there is no \"diff what I see with some alternate reality\".\n>\n> The majority use case I'm interested in is seeing net-positive changes\n> that happen in merge commits. Normally I take for granted that merge\n> commits have nothing meaningful in them (meaningful here defined as\n> something unexpected for a merge commit). But what if someone makes a\n> poor decision and does some crazy refactoring in 1 file and amends it\n> into a merge commit? Let's also say that these changes are done to a\n> file that wasn't modified in any parent (say a unrelated.txt next to\n> your color.txt). Since neither parent cares about that file for the\n> purposes of the merge, I am trying to make sense of a revision\n> specification that can be used to see what they did to that file.\n\nTo clarify, by \"doesn't exist\" I mean in the sense of the underlying\nobject model. I.e. it's conceivable to think of an SCM that would store\nwhat it thought was the merge resolution, followed by a diff from the\nuser on top.\n\nYou could even emulate that in git by having a policy of committing the\nunsolved merge resolution followed by the \"real\" solution, but that's\nnot how anyone uses it.\n\nThat doesn't mean you can't get what you want, notice how in my updated\nexample I've sneakily put a new README.txt into the merge commit. Now\nafter that merge let's see what merge-tree would do given the two\nun-merged tips (conflict):\n\n    $ git merge-tree base HEAD^1 HEAD^2\n    changed in both\n      base   100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 color.txt\n      our    100644 a9d1386a1d99728de010ad4044fb984886b57f33 color.txt\n      their  100644 a5b73ed2e94dafd12d02ab7b3ba3506cf8e642a2 color.txt\n    @@ -1 +1,5 @@\n    +<<<<<<< .our\n     red\n    +=======\n    +green\n    +>>>>>>> .their\n\nv.s. what diff-tree --cc reports:\n\n    $ git diff-tree --cc HEAD\n    b6e4d986d8850a2465fd5e648c670865bba59c05\n    diff --cc README.txt\n    index 0000000,0000000..ce01362\n    new file mode 100644\n    --- /dev/null\n    +++ b/README.txt\n    @@@ -1,0 -1,0 +1,1 @@@\n    ++hello\n    diff --cc color.txt\n    index a9d1386,a5b73ed..d1ed081\n    --- a/color.txt\n    +++ b/color.txt\n    @@@ -1,1 -1,1 +1,1 @@@\n    - red\n     -green\n    ++yellow\n\nThere's no reason we couldn't learn some mode where we walk the history\nand report differences like that, but note that the \"what would I get if\nI merged\" plumbing in git is still rather bad/incomplete in some areas,\nand also changes between versions of Git.\n\nE.g. Elijah Newren has been working on better rename detection recently,\nif you ran some version of that merge-tree (or however it's exposed, I'm\nnot very familiar with this area) you'd get a \"danger Will Robinson,\nmanually munged merge!\", even though it was a straightforward resolution\nof the suggested merge, and git's merge heuristics had just changed\nbetween versions.\n\n> Even ignoring that issue, the more concerning observation of mine is\n> that `diff @^!` produces any output at all. If you exclude both\n> parents, why do I see a diff for parent 2 (I see the complete diff of\n> the branch that was merged in)?\n\nI'm not sure, but I think this has to do with \"diff\" only taking two\nendpoints, see its manpage. It's curious that when I feed just the first\ntwo in manually I get the same as when it's passed to git-diff directly:\n\n    $ git diff -p @^!\n    diff --git a/README.txt b/README.txt\n    new file mode 100644\n    index 0000000..ce01362\n    --- /dev/null\n    +++ b/README.txt\n    @@ -0,0 +1 @@\n    +hello\n    diff --git a/color.txt b/color.txt\n    index a9d1386..d1ed081 100644\n    --- a/color.txt\n    +++ b/color.txt\n    @@ -1 +1 @@\n    -red\n    +yellow\n    $ git diff -p $(git rev-parse '@^!' | head -n 2)\n    diff --git a/README.txt b/README.txt\n    new file mode 100644\n    index 0000000..ce01362\n    --- /dev/null\n    +++ b/README.txt\n    @@ -0,0 +1 @@\n    +hello\n    diff --git a/color.txt b/color.txt\n    index a9d1386..d1ed081 100644\n    --- a/color.txt\n    +++ b/color.txt\n    @@ -1 +1 @@\n    -red\n    +yellow\n\nBut feeding all three into it gives output similar to --cc:\n\n    $ git diff -p $(git rev-parse '@^!')\n    diff --cc README.txt\n    index 0000000,0000000..ce01362\n    new file mode 100644\n    --- /dev/null\n    +++ b/README.txt\n    @@@ -1,0 -1,0 +1,1 @@@\n    ++hello\n    diff --cc color.txt\n    index a9d1386,a5b73ed..d1ed081\n    --- a/color.txt\n    +++ b/color.txt\n    @@@ -1,1 -1,1 +1,1 @@@\n    - red\n     -green\n    ++yellow\n\nI don't know. I spent a while looking into this a while ago and then\npaged it out of my brain :)\n\nTo reply to your downthread follow-up. The --cc mode won't work on all\ncommits due to what it's doing, but works on e.g. the merge commit\nproduced by my sample history.\n\n> Again, thank you for your example, you definitely made things very\n> clear for me. I see where the confusion is. And I think --cc is a good\n> way to get more context. At this point I'm just concerned about the\n> @^! behavior with merge commits & diff.\n"},{"id":"375043","messageId":"20190507155505.GA31344@esm","threadId":"51029","inReplyTo":"20190507145849.GA6313@archbookpro.localdomain","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Eckhard Maaß","fromEmail":"eckhard.s.maass@googlemail.com","sentAt":"2019-05-07T15:55:05Z","receivedAt":"2019-05-07T15:55:11Z","isPatch":false,"sender":{"key":"eckhard.s.maass@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/21984134?v=4"},"body":"On Tue, May 07, 2019 at 10:58:49AM -0400, Denton Liu wrote:\n> For more details, this code[2] just blindly diffs the first two\n> endpoints returned preceding `repo_init_revisions`.\n\nIf you throw in more than two endpoints, the result is a combined diff\nwith respect to the first commit. You can have some fun with that, eg\n\"git diff -c @ @~4 @^^2~4\".\n\n> Also, not to rehash an old discussion but I'll let this thread be my\n> argument *against* allowing range-notation in git-diff.\n\nWell, I think the most confusing part is to have this undocumented\naround. The range notation are basically just the same symbols for\ngit-diff, but the meaning seems to be quite different. If this is going\nto stay - shouldn't there be at least some documentation for people\ntrying such stuff out? And that we can point to? Or is there a reason to\nkeep this undocumented?\n\nGreetings,\nEckhard\n"},{"id":"375045","messageId":"CABPp-BECj___HneAYviE3SB=wU6OTcBi3S=+Un1sP6L4WJ7agA@mail.gmail.com","threadId":"51029","inReplyTo":"CAHd499BkdpsA2BdB0Hsv3xXzpMyMzW8CSuYf2gQX0Jf7OoYBGw@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-05-07T16:44:36Z","receivedAt":"2019-05-07T16:44:50Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, May 7, 2019 at 7:12 AM Robert Dailey <rcdailey.lists@gmail.com> wrote:\n> On Mon, May 6, 2019 at 6:52 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n\n> The majority use case I'm interested in is seeing net-positive changes\n> that happen in merge commits. Normally I take for granted that merge\n> commits have nothing meaningful in them (meaningful here defined as\n> something unexpected for a merge commit). But what if someone makes a\n> poor decision and does some crazy refactoring in 1 file and amends it\n> into a merge commit? Let's also say that these changes are done to a\n> file that wasn't modified in any parent (say a unrelated.txt next to\n> your color.txt). Since neither parent cares about that file for the\n> purposes of the merge, I am trying to make sense of a revision\n> specification that can be used to see what they did to that file.\n\nAn ability to find evil merges/cherry-picks/reverts by diffing a merge\ncommit to an auto-merge of some sort is something I am planning to\ntackle; I'm tracking it at\nhttps://bugs.chromium.org/p/git/issues/detail?id=12.  I'm working on\nfilter-repo first, then the merge rewrite, and then if I'm not\ndistracted by other stuff (a big if), then I'll tackle this.  Both\nthose other projects that are first in the list are rather large,\nthough, so it may be a while...\n\n> Even ignoring that issue, the more concerning observation of mine is\n> that `diff @^!` produces any output at all. If you exclude both\n> parents, why do I see a diff for parent 2 (I see the complete diff of\n> the branch that was merged in)?\n\nI think using ^! on merge commits with diff ought to be an error,\npersonally.  Not that I'd want to get into the battle of figuring out\nif someone did figure out what it means and find a valid use for it to\ndetermine if we need to deprecate or provide alternate syntax or who\nknows what for it.  I know it can be explained to be a combined diff\nof some sort, but that's it.  ^! as a post-fix operator on a non-merge\ncommit makes sense to me and is useful, even though it's an ugly hack\nand hideous usability-wise from a teaching or explaining perspective.\nWithout digging into the code in detail, I wouldn't for the life of me\nbe able to explain how ^! will work on merge commits with diff.\n\n> Again, thank you for your example, you definitely made things very\n> clear for me. I see where the confusion is. And I think --cc is a good\n> way to get more context. At this point I'm just concerned about the\n> @^! behavior with merge commits & diff.\n\nI am too, though my suggestion for that case is: please, for all that\nis good in the world, DON'T USE THAT.  EVER.  Whatever it might do, I\ncertainly don't want to waste any brain cycles supporting it or making\nsure it still works.  diff is an endpoint operation and people should\npass endpoints to diff, not ranges (with only two exceptions I can\nthink of: '^!' on non-merge commits and usage of the super confusing\nand ugly but also necessary '...' for diff against a merge-base.)\n\nHonestly, I am tempted to make git throw a warning whenever folks use\na range operator with diff other than the exceptions noted above.\nHmm...\n\nElijah\n"},{"id":"375337","messageId":"7cfb151e-346a-0eb5-aaa9-0a3e1da0fb2a@iee.org","threadId":"51029","inReplyTo":"CAHd499BkdpsA2BdB0Hsv3xXzpMyMzW8CSuYf2gQX0Jf7OoYBGw@mail.gmail.com","subject":"Re: Merge commit diff results are confusing and inconsistent","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2019-05-11T14:08:40Z","receivedAt":"2019-05-11T14:08:43Z","isPatch":false,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"Hi Robert,\n\nOn 07/05/2019 15:10, Robert Dailey wrote:\n> The majority use case I'm interested in is seeing net-positive changes\n> that happen in merge commits. Normally I take for granted that merge\n> commits have nothing meaningful in them (meaningful here defined as\n> something unexpected for a merge commit). But what if someone makes a\n> poor decision and does some crazy refactoring in 1 file and amends it\n> into a merge commit? Let's also say that these changes are done to a\n> file that wasn't modified in any parent (say a unrelated.txt next to\n> your color.txt). Since neither parent cares about that file for the\n> purposes of the merge, I am trying to make sense of a revision\n> specification that can be used to see what they did to that file.\n\nI see that you are specifically interested in seeing 'net-positive' changes.\n\nPart of the problem is that for a merge commit there are multiple \nchoices as to the implied initial central merge, where A and B are \ncombined to create X [which I just called the central merge], to which \nfurther changes are made to create the final merge commit C. (Note: X is \nnever committed, and is somewhat 'mythical')\n\nThese cases where there needs to be 'further changes', either to resolve \nconflicts because we never got a cleanly merged X, or the user added \nchanges, we an \"Evil Commit/Merge\". Definitions vary slightly between \ndifferent protagonists in the VCS world as to the best evil merge \nresolution starategies.\n\nFor your 'net-positive' changes, what is needed is to effectively \ngenerate that mythical clean initial merge X where either we delete from \nboth sides, or we have a simple addition only from one side \n(addition/deletion normally being of whole lines). It is only that way \nthat allows the changes from X to C to be addition only.\n\nUnfortunately there is currently no diff representation that does that, \nas there is no method of indicating that middle X state. In the worst \ncase there are always pathological cases.\n\nA similar problem exists for the “reuse recorded resolution” (rerere / \nredo) storage of conflict resolutions. At present there isn't a way of \nexchanging such resolutions in a mechanism similar to a diff. In fact I \nwas only just asking about that [1]  within the last two days! There is \nsome discussion about the rerere database in [2], should you want a look.\n--\nPhilip\n\n\n[1] rerere - \nhttps://public-inbox.org/git/b8e56556-6c83-9e37-38e9-ac67f51b5cd2@iee.org/\n[2] \nhttps://github.com/git/git/blob/master/Documentation/technical/rerere.txt\n"}]}