{"thread":{"id":"12635","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","startedAt":"2008-03-11T09:35:53Z","lastAt":"2008-03-12T01:57:20Z","messageCount":5,"participants":["colin@horizon.com","Lars Hjemli","Bruce Stephens","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"71677","messageId":"20080311093553.23191.qmail@science.horizon.com","threadId":"12635","inReplyTo":null,"subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"","fromEmail":"colin@horizon.com","sentAt":"2008-03-11T09:35:53Z","receivedAt":"2008-03-11T09:35:53Z","isPatch":true,"sender":{"key":"colin@horizon.com","avatar":null},"body":"> What's lacking is \"why this is a good idea\".\n\nSeconded.  A long time ago (and I'm too lazy to find a link), Linus\nexplained why disabling fast-forward merges was almost always a Bad Idea,\nand nobody has come up with a good reason why you'd want one since.\n\nBut from memory, suppose that you have two developers, each working on\ntheir own branch:\n\n     a--a--a <-- A's head\n    /\no--o\n    \\\n     b--b--b <-- B's head\n\nThen suppose that they merge back and forth to get to the same state.\nWith fast-forward merges, it will go like this:\n\nA merges from B:\n     a--a--a\n    /       \\\no--o         o <-- A's head\n    \\       /\n     b--b--b <-- B's head\n\nThen B merges from A:\n     a--a--a\n    /       \\\no--o         o <-- Both heads\n    \\       /\n     b--b--b\n\n\nAnd look, they are in sync and can go on to develop from a common base\nversion.  Future merges will do nothing.\n\n\nIf, instead, you have every merge generate a commit, then you get:\n     a--a--a\n    /       \\\no--o         o <-- A's head\n    \\       / \\\n     b--b--b---o <-- B's head\n\n     a--a--a\n    /       \\\no--o         o---o <-- A's head\n    \\       / \\ /\n     b--b--b---o <-- B's head\n\n     a--a--a\n    /       \\\no--o         o---o <-- A's head\n    \\       / \\ / \\\n     b--b--b---o---o <-- B's head\n\n.. and it never ends.  All of the merged commits are identical trees, but\nif you insist on creating a new commit object each time, you can generate\nan infinite number of bogus commits, and more to the point, A and B will\nnever actually agree on the current HEAD commit.\n\nWith more developers, you can make even more of a mess.\n\nWhat use does the \"--ff=never\" option have except to generate this cruft?\nFlexibility is useful only as long as it provides the ability to do\nsomething desirable.  There's no point to having a button that should\nnever be pushed.\n"},{"id":"71685","messageId":"8c5c35580803110309q2474c42q4758d618fca3cea@mail.gmail.com","threadId":"12635","inReplyTo":"20080311093553.23191.qmail@science.horizon.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Lars Hjemli","fromEmail":"lh@elementstorage.no","sentAt":"2008-03-11T10:09:22Z","receivedAt":"2008-03-11T10:09:22Z","isPatch":true,"sender":{"key":"lh@elementstorage.no","avatar":null},"body":"On Tue, Mar 11, 2008 at 10:35 AM,  <colin@horizon.com> wrote:\n> > What's lacking is \"why this is a good idea\".\n>\n>  Seconded.  A long time ago (and I'm too lazy to find a link), Linus\n>  explained why disabling fast-forward merges was almost always a Bad Idea,\n>  and nobody has come up with a good reason why you'd want one since.\n\nThe reason for --no-ff was twofold:\n* theoretical: when you want to record the integration of a topic branch\n* practical: when merging git-svn branches in git, git-svn dcommit\nwould update the wrong svn 'branch' if the merge was a fast-forward\n\nI originally needed --no-ff due to the 'practical' aspects (I used\ngit-svn when working with the day-job svn repository), but now that\nwe've switched to git (Hurray!) I'm still using --no-ff for the\n'theoretical' reason: our topic branches tend to be named after\nbugtracker tickets, so by recording the merge of such a branch we get\na very explicit note in our git log about when each ticket was\nresolved.\n\nYMMV.\n\n--\nlarsh\n"},{"id":"71689","messageId":"80r6eho3cs.fsf@tiny.isode.net","threadId":"12635","inReplyTo":"20080311093553.23191.qmail@science.horizon.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Bruce Stephens","fromEmail":"bruce.stephens@isode.com","sentAt":"2008-03-11T12:24:35Z","receivedAt":"2008-03-11T12:24:35Z","isPatch":true,"sender":{"key":"bruce.stephens@isode.com","avatar":null},"body":"colin@horizon.com writes:\n\n>> What's lacking is \"why this is a good idea\".\n\n[...]\n\n> .. and it never ends.  All of the merged commits are identical trees, but\n> if you insist on creating a new commit object each time, you can generate\n> an infinite number of bogus commits, and more to the point, A and B will\n> never actually agree on the current HEAD commit.\n>\n> With more developers, you can make even more of a mess.\n>\n> What use does the \"--ff=never\" option have except to generate this cruft?\n> Flexibility is useful only as long as it provides the ability to do\n> something desirable.  There's no point to having a button that should\n> never be pushed.\n\nIIUC what the new option is about is (optionally) forbidding merges.\nSo it's orthogonal to the existing --no-ff and --ff merge options.\n\nSo you *don't* get that kind of criss-crossing: if you've got a local\ncommit, the merge fails.  So you have to use rebase.  So it's not\nmaking the history more complex, it's linearizing it.\n\nNow surely you don't always want to do that, but it seems like a very\nconvenient option that you can generally have on, and switch off when\nyou intend to do a merge.\n"},{"id":"71691","messageId":"80lk4po2xp.fsf@tiny.isode.net","threadId":"12635","inReplyTo":"80r6eho3cs.fsf@tiny.isode.net","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Bruce Stephens","fromEmail":"bruce.stephens@isode.com","sentAt":"2008-03-11T12:33:38Z","receivedAt":"2008-03-11T12:33:38Z","isPatch":true,"sender":{"key":"bruce.stephens@isode.com","avatar":null},"body":"Bruce Stephens <bruce.stephens@isode.com> writes:\n\n> colin@horizon.com writes:\n\n[...]\n\n> IIUC what the new option is about is (optionally) forbidding merges.\n> So it's orthogonal to the existing --no-ff and --ff merge options.\n\nI'm wrong.  My apologies.\n\n[...]\n"},{"id":"71760","messageId":"7v1w6gbt6n.fsf@gitster.siamese.dyndns.org","threadId":"12635","inReplyTo":"20080311093553.23191.qmail@science.horizon.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-12T01:57:20Z","receivedAt":"2008-03-12T01:57:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"colin@horizon.com writes:\n\n>      a--a--a\n>     /       \\\n> o--o         o---o <-- A's head\n>     \\       / \\ /\n>      b--b--b---o <-- B's head\n>\n>      a--a--a\n>     /       \\\n> o--o         o---o <-- A's head\n>     \\       / \\ / \\\n>      b--b--b---o---o <-- B's head\n>\n> .. and it never ends.  All of the merged commits are identical trees, but\n> if you insist on creating a new commit object each time, you can generate\n> an infinite number of bogus commits, and more to the point, A and B will\n> never actually agree on the current HEAD commit.\n>\n> With more developers, you can make even more of a mess.\n>\n> What use does the \"--ff=never\" option have except to generate this cruft?\n\nJudicious use of non-fast-forward has a justification that is not too\nunreasonable.  That is, when you want to treat one lineage of history as\n\"more special than others\".\n\nIf your workflow is always to branch from the special branch (\"master\")\nwhen working on even a miniscule topic and merge that back to \"master\", if\nyou happen to have worked only on a single topic and the \"master\" was\nnever advanced during the time you worked on that topic, merging the topic\nback to \"master\" will result in a fast-forward.  When you look back that\nhistory, you won't be able to tell where the topic started and ended by\nfollowing the ancestry chain of the \"master\" branch.\n\nUsing \"never fast forward\" policy on such a special branch will be a way\nto make sure that all commits on the first-parent ancestry of that special\nbranch will be merges from something else, and by computing $it^1..$it^2\nfor a merge commit $it on the special branch, which merges the topic fully\ninto it, you can tell what commits the topic consisted of.\n\nWhen you have repeated merges from a topic to that special branch, this\ncomputation needs to be a bit more than just $it^1..$it^2 of the last\nmerge commit that merges the topic into \"master\".  E.g. you would have two\n\"should have been fast forward but artificially made into a real merge for\nthe purpose of peeing in the snow\" like this:\n\n           o---o---o---o---o \"topic\"\n          /     \\           \\\n      ---o-------*-----------* \"master\"\n \nBy following the first-parent ancestry of \"master\", you can tell that the\nfirst two changes on \"topic\" were accepted earlier and then three fixups\non top were incorporated much later, which is not something you can do if\nyou allowed fast-forward merge into \"master\".  Computing this history is\nsomewhat expensive but it is doable.  You have to follow the commit\nancestry of \"topic\", and for each commit you find, you would need to see\nwhich commit on the first-parent ancestry of \"master\" can reach it\n(e.g. the three topmost ones on \"topic\" can be reachable only by the last\nmerge on \"master\", while the remaining two can be reached by the previous\nmerge on \"master\").\n\nIn other words, if there is a globally special \"master\" history where\neverybody meets, forcing an artificial merge can have value.  However, for\nthis to work, you can never commit anything directly on such a special\n\"master\" branch, because directly committing on \"master\" is equivalent to\nfork a small topic branch that has a single commit on it, and immediately\nmerging it back with a fast-forward merge to \"master\".  So an artificial\nmerge can have value but that value can be had only with a disciplined\nworkflow.\n\nLast night I pulled a topic from Shawn which was a series of updates to\nthe bash completion script.  It was based on the tip of 'master' and\nresulted in a fast forward.  In git.git circle, it happens that my\n\"master\" history is not special at all.  I have \"trivially correct fixups\"\ndirectly committed on \"master\" all the time, and fast-forwarding to the\ntip of bash completion updates Shawn collected for me was exactly that,\nwith only different committer.  So even though I act as the top-level\nintegrator for git.git history, there was no reason to do non-fast-forward\nmerge at that point.  My tree is not that special.\n\nOn the other hand, I probably _could_ use non-ff to manage \"next\", which\nwill fork off of the tip of \"master\" after every major release.  In order\nto treat the first topic that will be merged into \"next\" just like other\nlater topics, it should be merged without fast-forward.  The latter topics\nwill never fast-forward (because topics fork off of \"master\" or \"maint\"\nand never from \"next\" itself) but the very first one can (because \"master\"\nand \"next\" will be at the same at that point), and allowing fast-forward\nwould mean the first topic after a major release is treated differently\nfrom others.  This is possible only because there is a fairly strict\ndiscipline of not committing anything directly on top of \"next\" and not\nforking off of it.\n"}]}