{"thread":{"id":"20779","subject":"How does git follow branch history across a merge commit?","startedAt":"2009-08-28T22:37:59Z","lastAt":"2009-08-30T00:29:57Z","messageCount":7,"participants":["Steven E. Harris","Junio C Hamano","Björn Steinbrink"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"122066","messageId":"8363c75zug.fsf@torus.sehlabs.com","threadId":"20779","inReplyTo":null,"subject":"How does git follow branch history across a merge commit?","fromName":"Steven E. Harris","fromEmail":"seh@panix.com","sentAt":"2009-08-28T22:37:59Z","receivedAt":"2009-08-28T22:37:59Z","isPatch":false,"sender":{"key":"seh@panix.com","avatar":"https://gravatar.com/avatar/d59ec0f7c010ee73cd67db381a5b865206fed17fd4278f12cdb6277db30033fc?d=mp&s=160"},"body":"This question concerns git's representation of merge commits.\n\nI understand that branches in git are references to commit vertices in a\ngraph, that each of these vertices have zero or more reference edges to\na parent (predecessor) vertex, and that merges create vertices with at\nleast two parents. Walking back in time from a branch head involves\ntraversing parent edges.\n\nBut what happens when such traversal reaches a merge commit? If one asks\ngit to move back, say, in time by a few days, or so many predecessors,\nhow does it know (or choose) which way to go upon reaching a merge\ncommit?\n\nI set up an experiment with two competing branches that require merging\nback together:\n\n,----\n| mkdir gitexp && cd gitexp && git init\n| $EDITOR file1.txt\n| git add file1.txt\n| git commit -m'Initial impound.'\n| $EDITOR file1.txt\n| git commit -a -m'Added a second line.'\n| \n| git checkout -b competition\n| $EDITOR file1.txt\n| git commit -a -m'Extended second line.'\n| \n| git checkout master\n| $EDITOR file1.txt\n| git commit -a -m'Added third line.'\n| \n| git checkout competition\n| $EDITOR file1.txt\n| git commit -a -m'Edited second line.'\n| $EDITOR file1.txt\n| git commit -a -m'Added an alternate third line.'\n| \n| git checkout master\n| git merge competition\n| # Edit to resolve conflict:\n| $EDITOR file1.txt\n| git commit -a\n| \n| git checkout 'HEAD^'\n| # At this point, git picked the predecessor along branch \"master\".\n`----\n\nWas it just luck that \"HEAD^\" referred to the predecessor that came from\nbranch \"master\" rather than branch \"competition\"?\n\n-- \nSteven E. Harris\n"},{"id":"122067","messageId":"7vskfbcy9n.fsf@alter.siamese.dyndns.org","threadId":"20779","inReplyTo":"8363c75zug.fsf@torus.sehlabs.com","subject":"Re: How does git follow branch history across a merge commit?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-08-28T23:30:12Z","receivedAt":"2009-08-28T23:30:12Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Steven E. Harris\" <seh@panix.com> writes:\n\n> Was it just luck that \"HEAD^\" referred to the predecessor that came from\n> branch \"master\" rather than branch \"competition\"?\n\nYes, if you want the second parent you would say HEAD^2.\n"},{"id":"122068","messageId":"7vmy5jbjkr.fsf@alter.siamese.dyndns.org","threadId":"20779","inReplyTo":"7vskfbcy9n.fsf@alter.siamese.dyndns.org","subject":"Re: How does git follow branch history across a merge commit?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-08-28T23:32:52Z","receivedAt":"2009-08-28T23:32:52Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Steven E. Harris\" <seh@panix.com> writes:\n>\n>> Was it just luck that \"HEAD^\" referred to the predecessor that came from\n>> branch \"master\" rather than branch \"competition\"?\n>\n> Yes, if you want the second parent you would say HEAD^2.\n\nAhh, I misread your question.  No, it is not luck.  If you merge\ncompetition into your master, the resulting commit will have your master\nas its first parent.  If check out competition and merge master in your\nexample, the resulting merge will have compatition as the first parent.\n"},{"id":"122069","messageId":"831vmv5wh7.fsf@torus.sehlabs.com","threadId":"20779","inReplyTo":"7vmy5jbjkr.fsf@alter.siamese.dyndns.org","subject":"Re: How does git follow branch history across a merge commit?","fromName":"Steven E. Harris","fromEmail":"seh@panix.com","sentAt":"2009-08-28T23:50:44Z","receivedAt":"2009-08-28T23:50:44Z","isPatch":false,"sender":{"key":"seh@panix.com","avatar":"https://gravatar.com/avatar/d59ec0f7c010ee73cd67db381a5b865206fed17fd4278f12cdb6277db30033fc?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> If you merge competition into your master, the resulting commit will\n> have your master as its first parent.  If check out competition and\n> merge master in your example, the resulting merge will have\n> compatition as the first parent.\n\nI see, having run a few more experiments to confirm this.\n\nI missed the point that merge commits are not \"predecessor neutral\";\nthey apparently have a bias indicating \"/this branch/ received content\nfrom /that branch/ (or /those branches/)\".\n\nTo try to recreate my confusing scenario, I tried this:\n\n,----\n| git checkout competition\n| git merge master\n| # This fast-forwarded \"competition\" be equivalent to \"master\".\n| git checkout 'HEAD^'\n| # This wound up again at \"master\"'s predecessor, not \"competition\"'s.\n`----\n\nIt seems that since fast-forward merges don't produce a commit, the\nmerge record remains in place recording that branch \"competition\" came\ninto branch \"master\". Even though we're checked out to branch\n\"competition\" here, following its history back in time requires some\nmanual intervention. Do you concur, or is my example perhaps flawed?\n\n-- \nSteven E. Harris\n"},{"id":"122070","messageId":"7vy6p38p4j.fsf@alter.siamese.dyndns.org","threadId":"20779","inReplyTo":"831vmv5wh7.fsf@torus.sehlabs.com","subject":"Re: How does git follow branch history across a merge commit?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-08-29T00:01:16Z","receivedAt":"2009-08-29T00:01:16Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Steven E. Harris\" <seh@panix.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> If you merge competition into your master, the resulting commit will\n>> have your master as its first parent.  If check out competition and\n>> merge master in your example, the resulting merge will have\n>> compatition as the first parent.\n>\n> I see, having run a few more experiments to confirm this.\n>\n> I missed the point that merge commits are not \"predecessor neutral\";\n> they apparently have a bias indicating \"/this branch/ received content\n> from /that branch/ (or /those branches/)\".\n\nWe are in principle pretty much neutral, but in certain places you\nobviously cannot avoid tiebreaking.  A commit object has to record parents\nin a bytestream in its representation, and inevitably one parent must come\nbefore another one.\n\nYou also must give your users some way to refer to each of its parents, so\nnaturally we count from one, and call them $it^ (== $it^1), $it^2, etc.\nWe do not have \"$it^?\" that lets you pick its parents at random, as we\nhaven't found a good use case for such a feature.\n\nOne place we tiebreak favoring earlier parents over later ones, even\nthough there is no logical reason to do so, is merge simplification [*1*].\nWhen more than one parents have the same tree, with respect to pathspec\ngiven, while traversing the history, we pick the earliest one, because\nthat is just as good as choosing one at random, and would give us a\nreproducible result.  For a similar reason, when blame follows a merge\nfrom identical parents, it assigns blame to earlier parents.\n\nAnd at the end-user workflow level, people tend to think \"I merge others\nwork into mine\", so the log family of commands have --first-parent option\nto follow only that ancestry, but that logic kicks in only while browsing,\nnot while building histories.\n\n[Footnote]\n\n*1* If you do not know what a merge simplification is, refer to e.g.\nhttp://gitster.livejournal.com/35628.html and notice there is a place\nwhere we follow \"a parent that is the same as the merge result\".\n"},{"id":"122072","messageId":"20090829002559.GA32479@atjola.homenet","threadId":"20779","inReplyTo":"831vmv5wh7.fsf@torus.sehlabs.com","subject":"Re: How does git follow branch history across a merge commit?","fromName":"Björn Steinbrink","fromEmail":"b.steinbrink@gmx.de","sentAt":"2009-08-29T00:25:59Z","receivedAt":"2009-08-29T00:25:59Z","isPatch":false,"sender":{"key":"b.steinbrink@gmx.de","avatar":"https://avatars.githubusercontent.com/u/230962?v=4"},"body":"On 2009.08.28 19:50:44 -0400, Steven E. Harris wrote:\n> I missed the point that merge commits are not \"predecessor neutral\";\n> they apparently have a bias indicating \"/this branch/ received content\n> from /that branch/ (or /those branches/)\".\n> \n> To try to recreate my confusing scenario, I tried this:\n> \n> ,----\n> | git checkout competition\n> | git merge master\n> | # This fast-forwarded \"competition\" be equivalent to \"master\".\n> | git checkout 'HEAD^'\n> | # This wound up again at \"master\"'s predecessor, not \"competition\"'s.\n> `----\n> \n> It seems that since fast-forward merges don't produce a commit, the\n> merge record remains in place recording that branch \"competition\" came\n> into branch \"master\".Even though we're checked out to branch\n> \"competition\" here, following its history back in time requires some\n> manual intervention. Do you concur, or is my example perhaps flawed?\n\nAs there is no merge commit, there's no \"merge record\" either.\n\"competition\" just got updated to reference the same commit as \"master\".\nBranch \"competition\" did not \"come into\" branch \"master\" either.\n\"competition\" and \"master\" are just the names of the branch heads. And\nthose just reference commits, which actually form the history and have\nno record of belonging to any branch head on their own.\n\nIOW: \"master\" and \"competition\" aren't branches as in \"a series of\ncommits with a fixed relation to each other\", but just branch heads,\nreferencing the tips where the branches grow.\n\nScenario A:\n\nGiven this history:\n\nA---B---C (master)\n \\\n  D---E (competition)\n\nIf you now merge \"master\" to \"competition\" you get:\n\nA---B---C (master)\n \\       \\\n  D---E---M (competition)\n\nWhere M is a merge commit with two parents, E being the first one, C\nbeing the second one. And that's everything that git knows, that M is a\nmerged of C and E. That E is the first parent is just a smart choice,\nnothing else.\n\n\nScenario B:\n\nGiven this history:\n\nA (competition)\n \\\n  B---C (master)\n\nIf you ask git to merge \"master\" to \"competition\", it will see that\nthere aren't any commits reachable through \"competition\" that aren't\nreachable through \"master\". So simply fast-forwarding \"competition won't\nmake you lose anything. Thus you get:\n\nA---B---C (master) (competition)\n\nThere's no trace of any merge, because it wasn't required. You can't\neven tell that a fast-forward happened from that history. You could as\nwell just have created \"competition\".\n\n\nThat said, if you want to force a merge commit (which can sometimes be\nuseful), you can use the --no-ff flag:\n\ngit checkout competition\ngit merge --no-ff master\n\nAnd this leads to:\n\nA------M (competition)\n \\    /\n  B---C (master)\n\n\nWith M's first parent being A, and the second parent being C.\n\n(Assuming that \"competition\" is a topic branch, this would be a bit\nweird though, as you produce a totally pointless merge commit, before\nyou even started working on your topic branch. OTOH doing this the other way\naround, i.e. forcing a merge commit when you merge a topic branch to\n\"master\" _can_ be useful. Don't over do it though).\n\nHTH\nBjörn\n"},{"id":"122091","messageId":"83ws4m3zzu.fsf@torus.sehlabs.com","threadId":"20779","inReplyTo":"7vy6p38p4j.fsf@alter.siamese.dyndns.org","subject":"Re: How does git follow branch history across a merge commit?","fromName":"Steven E. Harris","fromEmail":"seh@panix.com","sentAt":"2009-08-30T00:29:57Z","receivedAt":"2009-08-30T00:29:57Z","isPatch":false,"sender":{"key":"seh@panix.com","avatar":"https://gravatar.com/avatar/d59ec0f7c010ee73cd67db381a5b865206fed17fd4278f12cdb6277db30033fc?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> *1* If you do not know what a merge simplification is, refer to e.g.\n> http://gitster.livejournal.com/35628.html and notice there is a place\n> where we follow \"a parent that is the same as the merge result\".\n\nI read this article today with great interest. Had I known of it\nbeforehand, it would have changed some of my flawed assumptions evident\nin my post. Thanks for writing that. I'll be looking into the rest of\nthe articles there to learn more.\n\n-- \nSteven E. Harris\n"}]}