{"thread":{"id":"10441","subject":"unmerging feature branches","startedAt":"2007-10-23T15:24:45Z","lastAt":"2007-10-31T21:34:45Z","messageCount":16,"participants":["martin f krafft","Matthieu Moy","Linus Torvalds","Junio C Hamano","Alejandro Martinez Ruiz"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"57002","messageId":"20071023152445.GA10070@piper.oerlikon.madduck.net","threadId":"10441","inReplyTo":null,"subject":"unmerging feature branches","fromName":"martin f krafft","fromEmail":"madduck@madduck.net","sentAt":"2007-10-23T15:24:45Z","receivedAt":"2007-10-23T15:24:45Z","isPatch":false,"sender":{"key":"madduck@madduck.net","avatar":null},"body":"Dear list,\n\nLet's say I developed a feature Foo on a branch off master, and at\nsome point I merged it back into master (commit M) and published the\nrepo. Since M, a number of commits have been made onto master.\n\nNow I woul like to undo the merge.\n\nI could rebase (M+1)..master onto M^ (on the former master branch),\nbut that would orphan the commits between the merge point and the\ntip of master, which others are tracking.\n\nI'd love to have git-revert, but that cannot undo a multi-parent\ncommit.\n\nI could git-revert every commit on the feature branch between the\nbranch point and the merge point, even squash them into a single\ncommit, but that is a lot of work.\n\nAre there any other methods? Is it conceivable to let git-revert\nrevert a merging commit if you tell it somehow which of the two (or\nmore) parents are the ones you want undone, meaning that you'd like\nto keep the others?\n\n-- \nmartin | http://madduck.net/ | http://two.sentenc.es/\n \n\"a man who does not realise\n that he is half an animal\n is only half a man.\"\n                                                    -- thornton wilder\n \nspamtraps: madduck.bogus@madduck.net\n"},{"id":"57003","messageId":"vpqejflolb5.fsf@bauges.imag.fr","threadId":"10441","inReplyTo":"20071023152445.GA10070@piper.oerlikon.madduck.net","subject":"Re: unmerging feature branches","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@imag.fr","sentAt":"2007-10-23T16:19:42Z","receivedAt":"2007-10-23T16:19:42Z","isPatch":false,"sender":{"key":"git@matthieu-moy.fr","avatar":"https://avatars.githubusercontent.com/u/14709?v=4"},"body":"martin f krafft <madduck@madduck.net> writes:\n\n> Now I woul like to undo the merge.\n\nDirty solution: export the patch corresponding to the merge (diff\nM..M^), and apply it on master. If you have no conflicts, it should be\ndoable. If you have conflicts, it will probably be painfull.\n\n-- \nMatthieu\n"},{"id":"57004","messageId":"alpine.LFD.0.999.0710230922240.30120@woody.linux-foundation.org","threadId":"10441","inReplyTo":"20071023152445.GA10070@piper.oerlikon.madduck.net","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-23T16:50:59Z","receivedAt":"2007-10-23T16:50:59Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 23 Oct 2007, martin f krafft wrote:\n> \n> Are there any other methods? Is it conceivable to let git-revert\n> revert a merging commit if you tell it somehow which of the two (or\n> more) parents are the ones you want undone, meaning that you'd like\n> to keep the others?\n\nSo let me get this straight.. You have a merge \"M\" that is the result of \nmerging (possibly multiple) topic branches, and you now want to undo the \npart that *one* of them brought in?\n\nFirst off, let me say that to some degree, what you ask for is not \npossible. Why?\n\nSince you have pushed out the stuff, and don't want to rewrite history \n(which would result in trouble for down-streams - and I heartily approve), \nwhatever you do will always have that merge in the commit history.\n\nAnd that means that while you can certainly undo the *data* that the merge \nbrought in, git will always know that you already merged up that branch. \nWhich means that if you later decide that you *do* want to do the merge \nafter all, you now really cannot - trying to merge the branch later on \nwill just be a fast-forward, and you'll never get the actual changes from \nthat merge (since git knows you already have them!).\n\nSo you can revert the data, but then if you want to get it back, you'll \nneed to revert the revert - you cannot just merge the branch again. \n\nSo the first thing you need to realize is that \"revert\" does not revert \nhistory, it *only* reverts data. The fact that you did the merge will \nalways remain, although you could try to hack around even that by using \nthe 'grafts' file and trying to hide it (I really don't think it's a good \nidea, but sure, everything is \"possible\" in that sense).\n\nNow, that said, reverting the data is not that hard. There is not any \nsingle-command \"revert this arm of a merge\", but on the other hand, git \ncan certainly help you.\n\nThe way to do it is:\n\n\t# go back to just before the merge, create a \"fixup\" branch\n\t#\n\tgit branch -b fixup M^\n\n\t# merge all of it again, *except* the branch you didn't want to \n\t# merge (this example assumes that you had a four-way octopus \n\t# merge, and you now want to turn it into a three-way with the\n\t# next-to-last parent skipped):\n\t#\n\tgit merge -m \"fixed merge\" M^2 M^4\n\n\t# You now have \"fixup\" containing what you *wanted* it to be\n\t# after the original merge. Create a temporary branch that is \n\t# based on the merge and contains that state instead, and\n\t# apply the difference. \n\t#\n\tgit branch -b temporary M\n\tgit diff ..fixup | git-apply\n\tgit commit -m \"fixup commit\"\n\n\t# You now have the \"temporary\" branch that contains just the\n\t# diff that effectively undoes that one merge. Go back to the\n\t# tip of your development, and cherry-pick it to get git to\n\t# help you do a good job merging it with all the subsequent\n\t# development\n\t# \n\tgit checkout master\t# or whatever branch you used\n\tgit cherry-pick temporary\n\t.. do whatever you need to do to resolve it\n\t.. if it didn't go cleanly \n\n\t# Now, edit the commit message to talk about what you did\n\t#\n\tgit commit --amend\n\nor something to that effect.\n\nComplicated? Yes. The above is strictly speaking more complex than you may \nneed, but if you do it like the above, you get maximum help from git (ie \nyou *could* have tried to just apply the patch with \"git-apply\" directly \non the top of master, but if you do it like the above, then it's \nguaranteed that the patch that undoes the commit will apply cleanly, and \nyou then use \"git cherry-pick\" which uses the merge logic that can do a \nproper three-way merge with renames etc, so if there are conflicts or \nother things, the above will likely be the best way to do it)\n\nSo for simple cases, you can do the above more simply, but the above is \nfairly brainless and scriptable except for the *one* place where you \nactually move the changes forward (the single cherry-pick).\n\nThere are certainly other ways too. You could just \"git revert -n\" all the \ncommits that came in through the branch you didn't want to merge. That \ndoesn't work well if there were merges in that area, though, or of there \nwere changes that were common to all the branches (some of which also came \nin through *other* merges).\n\nSo the above (UNTESTED! Caveat emptor!) sequence is *one* way of doing it, \nand probably in the end the one that most closely represents what you want \nto do.\n\n\t\t\tLinus\n"},{"id":"57006","messageId":"20071023171611.GA18783@piper.oerlikon.madduck.net","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710230922240.30120@woody.linux-foundation.org","subject":"Re: unmerging feature branches","fromName":"martin f krafft","fromEmail":"madduck@madduck.net","sentAt":"2007-10-23T17:16:11Z","receivedAt":"2007-10-23T17:16:11Z","isPatch":false,"sender":{"key":"madduck@madduck.net","avatar":null},"body":"also sprach Linus Torvalds <torvalds@linux-foundation.org> [2007.10.23.1850 +0200]:\n> First off, let me say that to some degree, what you ask for is not \n> possible. Why?\n> \n> Since you have pushed out the stuff, and don't want to rewrite history \n> (which would result in trouble for down-streams - and I heartily approve), \n> whatever you do will always have that merge in the commit history.\n> \n> And that means that while you can certainly undo the *data* that the merge \n> brought in, git will always know that you already merged up that branch. \n\nThis is precisely what I meant, sorry for not being clear. This is\nwhat git-revert does...\n\n> So you can revert the data, but then if you want to get it back, you'll \n> need to revert the revert - you cannot just merge the branch again. \n\nOuch!\n\n> \t# You now have the \"temporary\" branch that contains just the\n> \t# diff that effectively undoes that one merge. Go back to the\n> \t# tip of your development, and cherry-pick it to get git to\n> \t# help you do a good job merging it with all the subsequent\n> \t# development\n\nAh, that's a good idea.\n\nThanks for your time and input!\n\nPS: this question of mine came out of a discussion on using Git for\nDebian packaging: what happens when we actually need to remove\na feature from one package to the next:\n  http://lists.madduck.net/pipermail/vcs-pkg/2007-October/000059.html\n\n-- \nmartin | http://madduck.net/ | http://two.sentenc.es/\n \n#define emacs eighty megabytes and constantly swapping.\n \nspamtraps: madduck.bogus@madduck.net\n"},{"id":"57007","messageId":"alpine.LFD.0.999.0710231026011.30120@woody.linux-foundation.org","threadId":"10441","inReplyTo":"20071023171611.GA18783@piper.oerlikon.madduck.net","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-23T17:40:16Z","receivedAt":"2007-10-23T17:40:16Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 23 Oct 2007, martin f krafft wrote:\n> \n> > So you can revert the data, but then if you want to get it back, you'll \n> > need to revert the revert - you cannot just merge the branch again. \n> \n> Ouch!\n\nWell, it's not necessarily \"Ouch\".\n\nIt actually depends on what you want to do. Sometimes this is a feature, \nand the thing is, it actually works for other things that just merge \ncommits.\n\nIn other words, think of what happens when you merge some development \nbranch, and then \"git revert\" a single commit from that branch - the exact \nsame thing will happen - future merges of that branch will *not* re-do the \ncommit, because you \"already have it\", and you reverted it after-the-fact.\n\nAnd in many ways, this is \"obviously\" what you want to happen!\n\nNow, I say \"obviously\" in quotes, because it's not at all obvious in an \nabsolute sense - it may be that you reverted the commit not because it was \nbuggy, but because your stable branch wasn't ready for it yet, and maybe \nin the future you do actually want the code that the revert reverted. So \nin that sense, nothing is really \"obvious\", and this is simply how things \nwork. But I think that it's easier to explain why git does something like \nthis when you speak about normal commits, and it all makes sense.\n\nWhen you revert the data from a merge, the exact same issue happens. A \nrevert (whether done by \"git revert\", or by the sequence of events I \ndescribed) very fundamentally undoes the *data* part, but leaves the \nhistory intact, and that has implications for future events that think \nabout history - which is mostly \"git merge\", but there are other thigns \ntoo.\n\nAs an example of \"other things\" that take history into account, think \nabout something like \"git rebase\". It's not a merge, but it also takes \nhistory into account in certain ways: in particular, it may be effectively \na \"series of cherry-picks\", but it actually takes the history of both \nbranches into account, and will not re-apply a patch that already exists \nin the target history.\n\nWhat does that mean? Let's say that both histories contain a patch X (not \nthe same commit, but the same patch), but one history also contains the \nrevert of X. Again, the revert reverts the data, but it does *not* revert \nthe history, so when you cherry-pick all the stuff from the other branch, \nX will *not* happen - even if it would apply cleanly, and even if a plain \n\"git cherry-pick\" would have redone it!\n\nWhy? History, again. Because \"git rebase\" sees that the commit already \nexisted, it won't even try to apply it again, never mind that it could \nhave worked. The \"revert\" didn't undo the history, just the data.\n\nSo a \"revert\" is fundamentally different from a \"undo\". Most of the time \nthat's exactly what you want, and I'm not pointing this out as a problem, \nI just wanted to point out that it has \"effects\". Sometimes the effects \nare good, sometimes they are bad, and while they are always very reliable \nand there's never any question about what git will do, people don't always \nthink like git, and whether the effects are \"good\" or \"bad\" is probably \nentirely up to whether they match users expectations or not.\n\nSo sometimes the behaviour of \"git revert\" will be exactly what people \nexpected and wanted (\"good, I'll never get that commit again when I pull, \nbecause I told git that I don't want that commit\"), and sometimes it will \n_not_ be what people expected and wanted (\"oh, I didn't get that commit, \neven though I was now ready for it - because I had reverted it back when I \nwas *not* ready for it\").\n\nSee? The logic is exactly the same in both cases, but one was good, the \nother bad, and the only difference was really the mindset of the user.\n\nA tool can't ever get \"mindset of the user\" differences right. At least \nnot until we add the \"esp option\" ;)\n\nSo I really don't want to push this as a problem or deficiency, I think \nit's a good thing. But it's a good thing only when people are *aware* of \nwhat \"revert\" really means.\n\n\t\t\tLinus\n"},{"id":"57010","messageId":"20071023180825.GA20343@piper.oerlikon.madduck.net","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710231026011.30120@woody.linux-foundation.org","subject":"Re: unmerging feature branches","fromName":"martin f krafft","fromEmail":"madduck@madduck.net","sentAt":"2007-10-23T18:08:25Z","receivedAt":"2007-10-23T18:08:25Z","isPatch":false,"sender":{"key":"madduck@madduck.net","avatar":null},"body":"also sprach Linus Torvalds <torvalds@linux-foundation.org> [2007.10.23.1940 +0200]:\n> > > So you can revert the data, but then if you want to get it back, you'll \n> > > need to revert the revert - you cannot just merge the branch again. \n> > \n> > Ouch!\n> \n> Well, it's not necessarily \"Ouch\".\n\nI said \"ouch\" only because I can foresee the confusion this may\ncause in collaborative package maintenance. One party merges the\nfeature branch, another reverts it, and a third (or the first)\nwonders why the feature isn't present despite having merged the\nbranch and must go through history to find the reverting commit,\nwhich is tied to the commit it reverts through nothing else than\na log message, at best.\n\n> In other words, think of what happens when you merge some\n> development branch, and then \"git revert\" a single commit from\n> that branch - the exact same thing will happen - future merges of\n> that branch will *not* re-do the commit, because you \"already have\n> it\", and you reverted it after-the-fact.\n[...]\n> When you revert the data from a merge, the exact same issue\n> happens. A revert (whether done by \"git revert\", or by the\n> sequence of events I described) very fundamentally undoes the\n> *data* part, but leaves the history intact, and that has\n> implications for future events that think about history - which is\n> mostly \"git merge\", but there are other thigns too.\n\nWhile this makes perfect sense, I am a bit thrown off now wrt two\nearlier posts by you (in another thread), where you said:\n\n  In other words, git never looks at individual commits when trying\n  to merge. It doesn't try to figure out what the \"meaning\" of the\n  changes are, it purely looks at the content.\n    -- http://marc.info/?l=git&m=119198488411957&w=2\n\n  Yes, history is interesting for historical reasons, and to explain\n  what the context was, but in many ways, history is exactly the\n  *wrong* thing to use when it comes to merging. You should look at\n  the end result, since people can - and do - come to the same\n  result through different ways.\n    -- http://marc.info/?l=git&m=119204501428555&w=2\n\nI master merged branch Foo, then reverted a commit introduced by\nFoo, and then Foo would be re-merged, the content *will* differ. So\nGit *has to* look at the list of commits in history to properly\nhandle reverts and *not* redo commits which have since been\nreverted.\n\nIs this correct?\n\n> As an example of \"other things\" that take history into account, think \n> about something like \"git rebase\". It's not a merge, but it also takes \n> history into account in certain ways: in particular, it may be effectively \n> a \"series of cherry-picks\", but it actually takes the history of both \n> branches into account, and will not re-apply a patch that already exists \n> in the target history.\n\nIn the light of the discussion in\n(http://marc.info/?t=119198137100002&r=1&w=2), I am now completely\nconfused. Or well, not confused, but I simply don't know anymore\nwhat Git does, and I thought I did.\n\n> What does that mean? Let's say that both histories contain a patch X (not \n> the same commit, but the same patch), but one history also contains the \n> revert of X. Again, the revert reverts the data, but it does *not* revert \n> the history, so when you cherry-pick all the stuff from the other branch, \n> X will *not* happen - even if it would apply cleanly, and even if a plain \n> \"git cherry-pick\" would have redone it!\n> \n> Why? History, again. Because \"git rebase\" sees that the commit already \n> existed, it won't even try to apply it again, never mind that it could \n> have worked. The \"revert\" didn't undo the history, just the data.\n\nHow can rebase know that the commit already existed when you're\nsaying above that it's about patch X, *not* the same commit?\n\n-- \nmartin | http://madduck.net/ | http://two.sentenc.es/\n \n\"a woman begins by resisting a man's advances and ends by blocking\n his retreat.\"\n                                                        -- oscar wilde\n \nspamtraps: madduck.bogus@madduck.net\n"},{"id":"57012","messageId":"alpine.LFD.0.999.0710231115060.30120@woody.linux-foundation.org","threadId":"10441","inReplyTo":"20071023180825.GA20343@piper.oerlikon.madduck.net","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-23T18:24:27Z","receivedAt":"2007-10-23T18:24:27Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 23 Oct 2007, martin f krafft wrote:\n> \n> While this makes perfect sense, I am a bit thrown off now wrt two\n> earlier posts by you (in another thread), where you said:\n> \n>   In other words, git never looks at individual commits when trying\n>   to merge. It doesn't try to figure out what the \"meaning\" of the\n>   changes are, it purely looks at the content.\n>     -- http://marc.info/?l=git&m=119198488411957&w=2\n\nThis is still true.\n\nGit never looks at individual commits when merging, it looks at the \n*history*.\n\nSo it does look at the commits only in the sense that it uses the \"shape\" \nof the history (which is obviously built up from many individual commits!) \nbut it never looks at any individual commit per se.\n\nAnd the behaviour of \"git revert\" comes *exactly* from the fact that git \nnever even bothers to look at the revert as a \"revert\" of history! A \nrevert is a normal data commit, and has absolutely zero impact on history \nitself. So the reason a merge will never give \"back\" the data over a \nrevert is that the data was already merged, since the history itself \ndidn't change!\n\n> I master merged branch Foo, then reverted a commit introduced by\n> Foo, and then Foo would be re-merged, the content *will* differ.\n\nNo. If you re-merge Foo, nothing at all happens! You're already merged. \nIt's a no-op.\n\nIf Foo has had *new* commits in the meantime, those new commits will show \nup, of course, but the old commits have absolutely zero effect, because \nthey will be part of the common history.\n\n> So Git *has to* look at the list of commits in history to properly \n> handle reverts and *not* redo commits which have since been reverted.\n> \n> Is this correct?\n\nNo, that's absolutely incorrect. You didn't understand what I meant.\n\nGit merge doesn't look at the revert at all, except (indorectly) when it \nbuilds up the history and it passes over it in order to find the common \nbase for the history.\n\n> > As an example of \"other things\" that take history into account, think \n> > about something like \"git rebase\". It's not a merge, but it also takes \n> > history into account in certain ways: in particular, it may be effectively \n> > a \"series of cherry-picks\", but it actually takes the history of both \n> > branches into account, and will not re-apply a patch that already exists \n> > in the target history.\n> \n> In the light of the discussion in\n> (http://marc.info/?t=119198137100002&r=1&w=2), I am now completely\n> confused. Or well, not confused, but I simply don't know anymore\n> what Git does, and I thought I did.\n\ngit-rebase is special. It really does look at each commit (obviously), \nsince it needs to *move* each commit. \n\nSo git-rebase has nothing at all to do with merges. They have similar \nbehaviour (quite often the end result is identical in the *data* - at \nleast that's the good case), but at the same time they are very different \nindeed. \n\nGit-merge *only* cares about the \"shape of the history\" (to find the \ncommon commit(s) to use as a merge base) and the actual data, while \"git \nrebase\" actually goes one commit at a time.\n\n> How can rebase know that the commit already existed when you're\n> saying above that it's about patch X, *not* the same commit?\n\nGit-rebase looks at the patch itself, using the \"patch fingerprint\", and \nwhen it goes through each commit, it skips commits that have already been \napplied. \n\nSee the man-pages for \"git-cherry\" and \"git-patch-id\".\n\n\t\tLinus\n"},{"id":"57016","messageId":"20071023191738.GA24575@piper.oerlikon.madduck.net","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710231115060.30120@woody.linux-foundation.org","subject":"Re: unmerging feature branches","fromName":"martin f krafft","fromEmail":"madduck@madduck.net","sentAt":"2007-10-23T19:17:38Z","receivedAt":"2007-10-23T19:17:38Z","isPatch":false,"sender":{"key":"madduck@madduck.net","avatar":null},"body":"also sprach Linus Torvalds <torvalds@linux-foundation.org> [2007.10.23.2024 +0200]:\n> So it does look at the commits only in the sense that it uses the \"shape\" \n> of the history (which is obviously built up from many individual commits!) \n> but it never looks at any individual commit per se.\n\nI don't follow what you mean with \"shape\". The following is\na history:\n\n o - x - o - o - o - m - o - A* - o - m2 - o - master\n      \\             /                /\n       `o - A - L -' - F - o - o - T' - branch\n\nA is a commit, A* is the commit which reverts (the data change by)\nA. L and F are to mark the last and first commits before and after\nthe first merge m. T is the tip of 'branch'\n\nAfter merge point m2, the change introduced by A will *not* be in\nmaster. This much makes sense.\n\nWhat did not make sense is how Git determines to leave it out. But\nI think that after drawing the above, it's now clear:\n\nby shape you mean the actual graph, and when 'branch' is merged into\nmaster at m2, Git goes back in time to conclude that master...L must\nalready be present in master due to the intersection of the two\nlines at m, and thus finds commit F as the \"oldest direct\ndescendant\" of m2. L is an older descendant of m2, but it's not\ndirect in the sense that there are multiple paths from m2 to L. Thus\nGit will only merge F..T at m2.\n\nOr as you put it:\n\n> If Foo has had *new* commits in the meantime, those new commits\n> will show up, of course, but the old commits have absolutely zero\n> effect, because they will be part of the common history.\n\nI think I am (moderately) clear again on the inner working of Git.\nSorry for the confusion.\n\n-- \nmartin | http://madduck.net/ | http://two.sentenc.es/\n \n\"the search for the perfect martini is a fraud. the perfect martini\n is a belt of gin from the bottle; anything else is the decadent\n trappings of civilization.\"\n                                                            -- t. k.\n \nspamtraps: madduck.bogus@madduck.net\n"},{"id":"57018","messageId":"7vir4x7hiu.fsf@gitster.siamese.dyndns.org","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710230922240.30120@woody.linux-foundation.org","subject":"Re: unmerging feature branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-10-23T19:33:29Z","receivedAt":"2007-10-23T19:33:29Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@linux-foundation.org> writes:\n\n> Now, that said, reverting the data is not that hard. There is not any \n> single-command \"revert this arm of a merge\", but on the other hand, git \n> can certainly help you.\n>\n> The way to do it is:\n>\n> \t# go back to just before the merge, create a \"fixup\" branch\n> \t#\n> \tgit branch -b fixup M^\n>\n> \t# merge all of it again, *except* the branch you didn't want to \n> \t# merge (this example assumes that you had a four-way octopus \n> \t# merge, and you now want to turn it into a three-way with the\n> \t# next-to-last parent skipped):\n> ...\n\nDesire to revert an octopus would, as you demonstrated, often be\nto revert only one arm, but I think allowing to revert a twohead\nmerge should be trivial.  If we define \"reverting a merge\" to\nalways revert all arms, then this should suffice.\n\ndiff --git a/builtin-revert.c b/builtin-revert.c\nindex a655c8e..719e293 100644\n--- a/builtin-revert.c\n+++ b/builtin-revert.c\n@@ -269,8 +269,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \n \tif (!commit->parents)\n \t\tdie (\"Cannot %s a root commit\", me);\n-\tif (commit->parents->next)\n-\t\tdie (\"Cannot %s a multi-parent commit.\", me);\n+\tif (action != REVERT && commit->parents->next)\n+\t\tdie (\"Cannot %s a merge commit.\", me);\n \tif (!(message = commit->buffer))\n \t\tdie (\"Cannot get commit message for %s\",\n \t\t\t\tsha1_to_hex(commit->object.sha1));\n\nNote that allowing cherry-pick by removing the above two lines\nallow replaying the data of a merge similar to a squash merge.\n"},{"id":"57019","messageId":"alpine.LFD.0.999.0710231221530.30120@woody.linux-foundation.org","threadId":"10441","inReplyTo":"20071023191738.GA24575@piper.oerlikon.madduck.net","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-23T19:38:26Z","receivedAt":"2007-10-23T19:38:26Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 23 Oct 2007, martin f krafft wrote:\n> \n> I don't follow what you mean with \"shape\". The following is\n> a history:\n> \n>  o - x - o - o - o - m - o - A* - o - m2 - o - master\n>       \\             /                /\n>        `o - A - L -' - F - o - o - T' - branch\n\nRight. And you can do two things when merging:\n\n - the wrong and insane thing: look at the *contents* of each commit, to \n   decide if a commit does a certain thing.\n\n - the git thing: never look at individual commits at all, except to find \n   the global history, what I called the \"shape\".\n\nSo git obviously very much *does* look at history,  but it does so only to \nfind the common point. So in when you create \"m2\", it looked at the \nhistory to see that the common point of the two branches was \"L\". And once \nit has found that, all the other stuff is totally irrelevant. It doesn't \nmatter if there are a million commits between 'm' and 'm2', the only thing \nthat mattered was the \"topology\" aka \"shape\" of the history.\n\nSee?\n\nSo git never actually cares about the individual commits A*, F, and T (or \nanything else) when it merges those two histories and created \"m2\".\n\nIt did *traverse* those commits, in order to find that \"Oh, the last \ncommon state was 'L'\", but it never looked at them in any other sense. \nThey were all individually uninteresting, and the only sense in which they \nmattered at all was as the incidental building blocks of the history.\n\nThat's what I mean by the \"shape\" of the history: when merging git does \nwalk the commits to see how it all holds together, but git doesn't then \ncare in any way what the commits *do* apart from how they connected up \nthe history of the two branches.\n\nAnd once git has found the common commit, it then just merges purely based \non the contents of the common commit and the two (or more, in the case of \noctopus merges) endpoints. So again, at that point it never looks at any \nof the individual commits, it only looks at what the *state* was.\n\n(This is all a bit more complex when thers is more than one \"common \ncommit\", but that's just a detail, and doesn't change the argument).\n\nAnd git-rebase is obviously totally different: git-rebase also finds the \ncommon points, but uses that to just discard all the shared history that \ncannot matter, and then it walks all the *unshared* commits to match them \nup and see which ones already look like they exist (as another commit, but \none that has the equivalent diff!), and which ones are worthy of trying to \nadd.\n\n> A is a commit, A* is the commit which reverts (the data change by)\n> A. L and F are to mark the last and first commits before and after\n> the first merge m. T is the tip of 'branch'\n> \n> After merge point m2, the change introduced by A will *not* be in\n> master. This much makes sense.\n\nYes.\n\n> What did not make sense is how Git determines to leave it out. But\n> I think that after drawing the above, it's now clear:\n> \n> by shape you mean the actual graph, and when 'branch' is merged into\n> master at m2, Git goes back in time to conclude that master...L must\n> already be present in master due to the intersection of the two\n> lines at m, and thus finds commit F as the \"oldest direct\n> descendant\" of m2. L is an older descendant of m2, but it's not\n> direct in the sense that there are multiple paths from m2 to L. Thus\n> Git will only merge F..T at m2.\n\nExactly.\n\n> Or as you put it:\n> \n> > If Foo has had *new* commits in the meantime, those new commits\n> > will show up, of course, but the old commits have absolutely zero\n> > effect, because they will be part of the common history.\n> \n> I think I am (moderately) clear again on the inner working of Git.\n> Sorry for the confusion.\n\nHey, I think these things are good to clarify, maybe somebody else didn't \nquite understand it. And the more \"graphical\" and concrete some problem \nis, the more likely people are to \"get it\". \n\nWhat is interesting is how the actual rules that git follows are *really* \nsimple. But they result in all this fairly complex, almost \"emergent\" \nbehaviour. It's like the basic data structures: in many ways, git really \nonly has those four basic object types, and you can really descibe \neverything git does in terms of those very simple core data structures: \nbut a *repository* is certainly not something simple.\n\nBut the final \"behaviour\" really all comes from the interactions of \nthings. The rules are all really really simple, the data structures are \nall totally trivial, but the possibility for interactions causes all the \nexcitement.\n\n\t\t\tLinus\n"},{"id":"57020","messageId":"alpine.LFD.0.999.0710231239300.30120@woody.linux-foundation.org","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710231221530.30120@woody.linux-foundation.org","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-23T19:46:51Z","receivedAt":"2007-10-23T19:46:51Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 23 Oct 2007, Linus Torvalds wrote:\n>\n> > by shape you mean the actual graph, and when 'branch' is merged into\n> > master at m2, Git goes back in time to conclude that master...L must\n> > already be present in master due to the intersection of the two\n> > lines at m, and thus finds commit F as the \"oldest direct\n> > descendant\" of m2. L is an older descendant of m2, but it's not\n> > direct in the sense that there are multiple paths from m2 to L. Thus\n> > Git will only merge F..T at m2.\n> \n> Exactly.\n\nSide note: strictly speaking, git will not merge \"F..T\" in the sense that \nit never actually even _looks_ at any of the commits in that range per se.\n\nSo what it really does is to look at state of the common point ('L') and \nthen the states of the end-points, and merge things based purely based on \nthat state, and then join the histories up.\n\nYes, that *effectively* means merging all the changes from 'F'..'T', but I \nwant again to point out that the actual changes done by any of the \nindividual commits in that range are never even looked at. They really are \ntotally irrelevant on their own.\n\nSo if 'F' did a lot of changes and 'T' undid most of them, the merge \nalgorithm will not ever even *see* those changes. They were irrelevant. \nIt's not that git sees the changes and then sees that 'T' undid them: git \nwill literally never actually even look at them in the first place!\n\nThat's what I mean by only taking \"state\" into account. It didn't matter \nwhat any individual commit did. Git won't even have looked at the commit, \nother than to find its parent. When git merges, it literally looks at just \nthe end results, and the last common state(s).\n\nSo history matters a great deal to merging, but it only matters in the \nglobal \"shape\" sense, never in the \"per-commit\" sense.\n\n\t\t\tLinus\n"},{"id":"57021","messageId":"alpine.LFD.0.999.0710231247301.30120@woody.linux-foundation.org","threadId":"10441","inReplyTo":"7vir4x7hiu.fsf@gitster.siamese.dyndns.org","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-23T19:49:12Z","receivedAt":"2007-10-23T19:49:12Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 23 Oct 2007, Junio C Hamano wrote:\n> \n> Desire to revert an octopus would, as you demonstrated, often be\n> to revert only one arm, but I think allowing to revert a twohead\n> merge should be trivial.  If we define \"reverting a merge\" to\n> always revert all arms, then this should suffice.\n\nThe only reason I don't like this is that it kind of assumes that the \nmainline is the first parent. \n\nMaybe I'd like to revert a merge, but I want to revert a merge that \nsomebody *else* did, and maybe it was the first-hand parent I don't like. \n\nThose kinds of issues don't exist with non-merge commits: there's never \nany question \"which side\" to revert.\n\n\t\t\tLinus\n"},{"id":"57025","messageId":"7vejfl7eqx.fsf_-_@gitster.siamese.dyndns.org","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710231247301.30120@woody.linux-foundation.org","subject":"[PATCH] revert/cherry-pick: work on merge commits as well","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-10-23T20:33:26Z","receivedAt":"2007-10-23T20:33:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usually you cannot revert a merge because you do not know which\nside of the merge should be considered the mainline (iow, what\nchange to reverse).\n\nWith this patch, cherry-pick and revert learn -m (--mainline)\noption that lets you specify the parent number (starting from 1)\nof the mainline, so that you can:\n\n\tgit revert -m 1 $merge\n\nto reverse the changes introduced by the $merge commit relative\nto its first parent, and:\n\n\tgit cherry-pick -m 2 $merge\n\nto replay the changes introduced by the $merge commit relative\nto its second parent.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n Linus Torvalds <torvalds@linux-foundation.org> writes:\n\n > On Tue, 23 Oct 2007, Junio C Hamano wrote:\n >> \n >> Desire to revert an octopus would, as you demonstrated, often be\n >> to revert only one arm, but I think allowing to revert a twohead\n >> merge should be trivial.  If we define \"reverting a merge\" to\n >> always revert all arms, then this should suffice.\n >\n > The only reason I don't like this is that it kind of assumes that the \n > mainline is the first parent. \n >\n > Maybe I'd like to revert a merge, but I want to revert a merge that \n > somebody *else* did, and maybe it was the first-hand parent I don't like. \n >\n > Those kinds of issues don't exist with non-merge commits: there's never \n > any question \"which side\" to revert.\n\n Fair enough.  How about this?\n\n Documentation/git-cherry-pick.txt |    9 +++++++-\n Documentation/git-revert.txt      |    9 +++++++-\n builtin-revert.c                  |   42 ++++++++++++++++++++++++++++++------\n git-compat-util.h                 |   13 +++++++++++\n 4 files changed, 64 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt\nindex 76a2edf..937c4a7 100644\n--- a/Documentation/git-cherry-pick.txt\n+++ b/Documentation/git-cherry-pick.txt\n@@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit\n \n SYNOPSIS\n --------\n-'git-cherry-pick' [--edit] [-n] [-x] <commit>\n+'git-cherry-pick' [--edit] [-n] [-m parent-number] [-x] <commit>\n \n DESCRIPTION\n -----------\n@@ -44,6 +44,13 @@ OPTIONS\n \tdescribed above, and `-r` was to disable it.  Now the\n \tdefault is not to do `-x` so this option is a no-op.\n \n+-m parent-number|--mainline parent-number::\n+\tUsually you cannot revert a merge because you do not know which\n+\tside of the merge should be considered the mainline.  This\n+\toption specifies the parent number (starting from 1) of\n+\tthe mainline and allows cherry-pick to replay the change\n+\trelative to the specified parent.\n+\n -n|--no-commit::\n \tUsually the command automatically creates a commit with\n \ta commit log message stating which commit was\ndiff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt\nindex 69db498..3457c40 100644\n--- a/Documentation/git-revert.txt\n+++ b/Documentation/git-revert.txt\n@@ -7,7 +7,7 @@ git-revert - Revert an existing commit\n \n SYNOPSIS\n --------\n-'git-revert' [--edit | --no-edit] [-n] <commit>\n+'git-revert' [--edit | --no-edit] [-n] [-m parent-number] <commit>\n \n DESCRIPTION\n -----------\n@@ -27,6 +27,13 @@ OPTIONS\n \tmessage prior committing the revert. This is the default if\n \tyou run the command from a terminal.\n \n+-m parent-number|--mainline parent-number::\n+\tUsually you cannot revert a merge because you do not know which\n+\tside of the merge should be considered the mainline.  This\n+\toption specifies the parent number (starting from 1) of\n+\tthe mainline and allows revert to reverse the change\n+\trelative to the specified parent.\n+\n --no-edit::\n \tWith this option, `git-revert` will not start the commit\n \tmessage editor.\ndiff --git a/builtin-revert.c b/builtin-revert.c\nindex a655c8e..bfed69d 100644\n--- a/builtin-revert.c\n+++ b/builtin-revert.c\n@@ -19,9 +19,9 @@\n  * Copyright (c) 2005 Junio C Hamano\n  */\n \n-static const char *revert_usage = \"git-revert [--edit | --no-edit] [-n] <commit-ish>\";\n+static const char *revert_usage = \"git-revert [--edit | --no-edit] [-n] [-m parent-number] <commit-ish>\";\n \n-static const char *cherry_pick_usage = \"git-cherry-pick [--edit] [-n] [-r] [-x] <commit-ish>\";\n+static const char *cherry_pick_usage = \"git-cherry-pick [--edit] [-n] [-m parent-number] [-r] [-x] <commit-ish>\";\n \n static int edit;\n static int replay;\n@@ -29,6 +29,7 @@ static enum { REVERT, CHERRY_PICK } action;\n static int no_commit;\n static struct commit *commit;\n static int needed_deref;\n+static int mainline;\n \n static const char *me;\n \n@@ -58,6 +59,12 @@ static void parse_options(int argc, const char **argv)\n \t\telse if (!strcmp(arg, \"-x\") || !strcmp(arg, \"--i-really-want-\"\n \t\t\t\t\"to-expose-my-private-commit-object-name\"))\n \t\t\treplay = 0;\n+\t\telse if (!strcmp(arg, \"-m\") || !strcmp(arg, \"--mainline\")) {\n+\t\t\tif (++i >= argc ||\n+\t\t\t    strtol_i(argv[i], 10, &mainline) ||\n+\t\t\t    mainline <= 0)\n+\t\t\t\tusage(usage_str);\n+\t\t}\n \t\telse if (strcmp(arg, \"-r\"))\n \t\t\tusage(usage_str);\n \t}\n@@ -234,7 +241,7 @@ static int merge_recursive(const char *base_sha1,\n static int revert_or_cherry_pick(int argc, const char **argv)\n {\n \tunsigned char head[20];\n-\tstruct commit *base, *next;\n+\tstruct commit *base, *next, *parent;\n \tint i;\n \tchar *oneline, *reencoded_message = NULL;\n \tconst char *message, *encoding;\n@@ -269,8 +276,29 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \n \tif (!commit->parents)\n \t\tdie (\"Cannot %s a root commit\", me);\n-\tif (commit->parents->next)\n-\t\tdie (\"Cannot %s a multi-parent commit.\", me);\n+\tif (commit->parents->next) {\n+\t\t/* Reverting or cherry-picking a merge commit */\n+\t\tint cnt;\n+\t\tstruct commit_list *p;\n+\n+\t\tif (!mainline)\n+\t\t\tdie(\"Commit %s is a merge but no -m option was given.\",\n+\t\t\t    sha1_to_hex(commit->object.sha1));\n+\n+\t\tfor (cnt = 1, p = commit->parents;\n+\t\t     cnt != mainline && p;\n+\t\t     cnt++)\n+\t\t\tp = p->next;\n+\t\tif (cnt != mainline || !p)\n+\t\t\tdie(\"Commit %s does not have parent %d\",\n+\t\t\t    sha1_to_hex(commit->object.sha1), mainline);\n+\t\tparent = p->item;\n+\t} else if (0 < mainline)\n+\t\tdie(\"Mainline was specified but commit %s is not a merge.\",\n+\t\t    sha1_to_hex(commit->object.sha1));\n+\telse\n+\t\tparent = commit->parents->item;\n+\n \tif (!(message = commit->buffer))\n \t\tdie (\"Cannot get commit message for %s\",\n \t\t\t\tsha1_to_hex(commit->object.sha1));\n@@ -299,14 +327,14 @@ static int revert_or_cherry_pick(int argc, const char **argv)\n \t\tchar *oneline_body = strchr(oneline, ' ');\n \n \t\tbase = commit;\n-\t\tnext = commit->parents->item;\n+\t\tnext = parent;\n \t\tadd_to_msg(\"Revert \\\"\");\n \t\tadd_to_msg(oneline_body + 1);\n \t\tadd_to_msg(\"\\\"\\n\\nThis reverts commit \");\n \t\tadd_to_msg(sha1_to_hex(commit->object.sha1));\n \t\tadd_to_msg(\".\\n\");\n \t} else {\n-\t\tbase = commit->parents->item;\n+\t\tbase = parent;\n \t\tnext = commit;\n \t\tset_author_ident_env(message);\n \t\tadd_message_to_msg(message);\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex f23d934..a12c36b 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -376,4 +376,17 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+static inline int strtol_i(char const *s, int base, int *result)\n+{\n+\tlong ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\tul = strtol(s, &p, base);\n+\tif (errno || *p || p == s || (int) ul != ul)\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n #endif\n-- \n1.5.3.4.1324.ga7925\n"},{"id":"57736","messageId":"20071031211658.GA5430@inspiron","threadId":"10441","inReplyTo":"alpine.LFD.0.999.0710231026011.30120@woody.linux-foundation.org","subject":"Re: unmerging feature branches","fromName":"Alejandro Martinez Ruiz","fromEmail":"alex@flawedcode.org","sentAt":"2007-10-31T21:16:58Z","receivedAt":"2007-10-31T21:16:58Z","isPatch":false,"sender":{"key":"alex@flawedcode.org","avatar":"https://gravatar.com/avatar/aae35e2f84230bfbc19c9b840ef8437fcb110f5316ea91197e3cd96c4bb4bf30?d=mp&s=160"},"body":"On Tue 23 Oct 2007, 10:40, Linus Torvalds wrote:\n> \n> So a \"revert\" is fundamentally different from a \"undo\". Most of the time \n> \n> [cut]\n> \n> So sometimes the behaviour of \"git revert\" will be exactly what people \n> expected and wanted (\"good, I'll never get that commit again when I pull, \n> because I told git that I don't want that commit\"), and sometimes it will \n> _not_ be what people expected and wanted (\"oh, I didn't get that commit, \n> even though I was now ready for it - because I had reverted it back when I \n> was *not* ready for it\").\n> \n> See? The logic is exactly the same in both cases, but one was good, the \n> other bad, and the only difference was really the mindset of the user.\n> \n> A tool can't ever get \"mindset of the user\" differences right. At least \n> not until we add the \"esp option\" ;)\n> \n> So I really don't want to push this as a problem or deficiency, I think \n> it's a good thing. But it's a good thing only when people are *aware* of \n> what \"revert\" really means.\n\nSo how about an \"undo\" command or a switch for revert with a special\nmeaning like \"hey, this one is a nice commit, but it ain't ready yet,\nI'd like you to ignore I ever committed the thing when merging or\nrebasing again, thanks\"?\n\nAlex\n"},{"id":"57741","messageId":"20071031212730.GA32170@piper.oerlikon.madduck.net","threadId":"10441","inReplyTo":"20071031211658.GA5430@inspiron","subject":"Re: unmerging feature branches","fromName":"martin f krafft","fromEmail":"madduck@madduck.net","sentAt":"2007-10-31T21:27:30Z","receivedAt":"2007-10-31T21:27:30Z","isPatch":false,"sender":{"key":"madduck@madduck.net","avatar":null},"body":"also sprach Alejandro Martinez Ruiz <alex@flawedcode.org> [2007.10.31.2216 +0100]:\n> So how about an \"undo\" command or a switch for revert with a special\n> meaning like \"hey, this one is a nice commit, but it ain't ready yet,\n> I'd like you to ignore I ever committed the thing when merging or\n> rebasing again, thanks\"?\n\nRevert does exactly that, by reverting the content. That's all Git\ncares about. Does\n\n  http://lists-archives.org/git/634475-unmerging-feature-branches.html\n\nmake it clear?\n\n-- \nmartin | http://madduck.net/ | http://two.sentenc.es/\n \n\"getting a scsi chain working is perfectly simple if you remember that\n there must be exactly three terminations: one on one end of the\n cable, one on the far end, and the goat, terminated over the scsi\n chain with a silver-handled knife whilst burning *black* candles.\"\n                                                     -- anthony deboer\n \nspamtraps: madduck.bogus@madduck.net\n"},{"id":"57746","messageId":"alpine.LFD.0.999.0710311429080.3342@woody.linux-foundation.org","threadId":"10441","inReplyTo":"20071031211658.GA5430@inspiron","subject":"Re: unmerging feature branches","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-10-31T21:34:45Z","receivedAt":"2007-10-31T21:34:45Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Wed, 31 Oct 2007, Alejandro Martinez Ruiz wrote:\n> \n> So how about an \"undo\" command or a switch for revert with a special\n> meaning like \"hey, this one is a nice commit, but it ain't ready yet,\n> I'd like you to ignore I ever committed the thing when merging or\n> rebasing again, thanks\"?\n\nThere is only one undo command, and that one we've had since day 1:\n\n\tgit reset --hard <state-you-want-to-go-back-to>\n\nwill happily undo anything at all (including an earlier undo, apart from \nuncommitted dirty tree state, which is gone, gone, gone after that \n\"undo\" and can not be retrieved).\n\nThat's the only real true \"undo\" with clear semantics - it actually does \nundo the whole history.\n\nBut the kind of \"undo\" you wish for is not really possible. It implies a \nlevel of semantics that the system just doesn't know or care about. It \nalso implies that anything else than the shape of history would matter for \nmerging, which is just anathema to everything that makes git good in the \nfirst place.\n\nThat said, in practice, this really seldom does come up. You can often use \n\"git revert\" as that kind of undo, and when you later do the merge, and \nthe other side has fixed up the code, it's (a) likely going to be obvious \nin the conflicts and (b) if the fixes were to infrastructure and you had \nno conflicts, it's really easy to just revert the revert too!\n\n\t\t\tLinus\n"}]}