{"thread":{"id":"16254","subject":"overly smart rebase - bug or feature?","startedAt":"2008-11-10T21:23:34Z","lastAt":"2008-11-12T22:04:35Z","messageCount":7,"participants":["Fedor Sergeev","Junio C Hamano","Avery Pennarun"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"95392","messageId":"20081110212333.GU6799@sun.com","threadId":"16254","inReplyTo":null,"subject":"overly smart rebase - bug or feature?","fromName":"Fedor Sergeev","fromEmail":"fedor.sergeev@sun.com","sentAt":"2008-11-10T21:23:34Z","receivedAt":"2008-11-10T21:23:34Z","isPatch":false,"sender":{"key":"fedor.sergeev@sun.com","avatar":null},"body":"Folks,\n\nI have recently hit a behavior which might well be a feature, \nbut it was very surprising (in a bad sense) to me.\n\nI was trying to rebase a branch with changes in some file onto a branch\nwhere this file was recently deleted. I would expect rebase to fail and \nsuggest me to  resolve conflict manually.\nHowever it somehow succeeded managing to find another file to patch instead \nof the initial one:\n\n] cat git-rebase-bug.sh\n#!/bin/sh\ngit init\n# create three files with the same contents\nperl -e ' for ($i=0; $i < 10; $i++) { print \"$i\\n\" } ' >Makefile\ncp Makefile Makefile1\ncp Makefile Makefile2\ngit add .\ngit commit -m\"created 3 makefiles\"\n# delete one file\ngit rm Makefile\ngit commit -m\"deleted 1 makefile\"\n# go to another branch, one step back\ngit checkout -b mod HEAD^\n# modify contents of the file deleted in master branch\necho \"#10\" >>Makefile\ngit add -u\ngit commit -m\"modified 1 makefile\"\n# now rebase \"mod\" on top of \"master\" not expecting it to succeed\ngit rebase master mod\n]\n\n] mkdir git-bug; cd git-bug\n] ../git-rebase-bug.sh\n....\nFirst, rewinding head to replay your work on top of it...\nApplying: modified 1 makefile\nerror: Makefile: does not exist in index\nUsing index info to reconstruct a base tree...\nFalling back to patching base and 3-way merge...\n]\n\nNow if I look at the rebase result I see that it chose to patch \"Makefile2\" \ninstead of my lovely \"Makefile\" (why not Makefile1, btw ;) ):\n\n] git log --stat -1 --pretty=oneline\nce0101fc7884bce3eb9724b75d654e7c40abf0fd modified 1 makefile\n Makefile2 |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n]\n\nI has always agreed with the claim that simple but reliable merge\n(rebase, whatever) is much better than smartass one smarter than yourself.\n\nAnd, to be honest, both merge and cherry-pick do not try to play smart:\n\n] git reset --hard mod@{1}\n] git checkout master\n] git merge mod\nCCONFLICT (delete/modify): Makefile deleted in HEAD and modified in mod. Version mod of Makefile left in tree.\nAutomatic merge failed; fix conflicts and then commit the result.\n] git reset --hard\n] git cherry-pick mod\nAutomatic cherry-pick failed.  After resolving the conflicts,\nmark the corrected paths with 'git add <paths>' or 'git rm <paths>' and commit the result.\nWhen commiting, use the option '-c f782a81' to retain authorship and message.\n]\n\nSo, why rebase is smarter?\n\nYeah, and if it matters I tried it on 1.6.0.2 and 1.5.3.8 on Solaris and Linux.\n\nbest regards,\n  Fedor.\nPS I had problems reaching this list, thus ccing Junio explicitly.\nI'm not on the list, btw..\n"},{"id":"95399","messageId":"7vod0n41i5.fsf@gitster.siamese.dyndns.org","threadId":"16254","inReplyTo":"20081110212333.GU6799@sun.com","subject":"Re: overly smart rebase - bug or feature?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-11-10T23:14:42Z","receivedAt":"2008-11-10T23:14:42Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fedor Sergeev <Fedor.Sergeev@Sun.COM> writes:\n\n> I have recently hit a behavior which might well be a feature, \n> but it was very surprising (in a bad sense) to me.\n\nIt is a feature misfiring.\n\nRebase is essentially a repeated cherry-pick, and a cherry-pick of commit\nA on top of commit B is done by a simplified 3-way merge between A and B\nusing the parent of A as the common ancestor.\n\n     A                          A'\n    /                          /\n   A^... pseudo history ...---B\n\nWhen your history has renamed Makefile to Makefile2 (thereby losing\nMakefile) while transition from A^ to A modified Makefile, the difference\nbetween A^ to A that is applied to B to produce A' contains only the\nchange about Makefile (and does not talk about the unchangedness of\nMakefile1 nor Makefile2 --- in fact, when A' is created, the machinery\ndoes not even know if A^ and A had Makefile1 or Makefile2).\n\nWhen applying the change to Makefile, it notices that B does not have\nMakefile, but there is a path that is _identical_ to the preimage your\nchange applies to (namely, Makefile2).  To support people who rename\nMakefile to Makefile2 in the history that led to B, rebase (actually the\nunderlying \"am -3\" it calls is where this rename detection smart lies)\napplies the changes to the \"renamed\" path.\n\nYou might be able to work this around by forcing rebase not to use the\nsimplified 3-way merge, by saying \"rebase -m\".\n"},{"id":"95403","messageId":"32541b130811101531r4b92edc3wfdfb49dc0e5119f4@mail.gmail.com","threadId":"16254","inReplyTo":"7vod0n41i5.fsf@gitster.siamese.dyndns.org","subject":"Re: overly smart rebase - bug or feature?","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2008-11-10T23:31:25Z","receivedAt":"2008-11-10T23:31:25Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Mon, Nov 10, 2008 at 6:14 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> When applying the change to Makefile, it notices that B does not have\n> Makefile, but there is a path that is _identical_ to the preimage your\n> change applies to (namely, Makefile2).  To support people who rename\n> Makefile to Makefile2 in the history that led to B, rebase (actually the\n> underlying \"am -3\" it calls is where this rename detection smart lies)\n> applies the changes to the \"renamed\" path.\n\nBut isn't rename detection in this case rather suspicious, since:\n\n- the preimage already had Makefile, Makefile1, and Makefile2, thus it\nis not a rename, but at most a copy, and not even a newly-created copy\nin either branch;\n\n- *two* different files match the original Makefile, but rebase has\nrandomly selected one but not the other;\n\n- (I haven't verified this claim) cherry-pick and merge both correctly\nidentify the problem as a delete/modify conflict?\n\nIt seems that rebase should have bailed out for at least one of these\nthree reasons.\n\nAvery\n"},{"id":"95406","messageId":"20081110233649.GI6799@sun.com","threadId":"16254","inReplyTo":"7vod0n41i5.fsf@gitster.siamese.dyndns.org","subject":"Re: overly smart rebase - bug or feature?","fromName":"Fedor Sergeev","fromEmail":"fedor.sergeev@sun.com","sentAt":"2008-11-10T23:36:49Z","receivedAt":"2008-11-10T23:36:49Z","isPatch":false,"sender":{"key":"fedor.sergeev@sun.com","avatar":null},"body":"On Mon, Nov 10, 2008 at 03:14:42PM -0800, Junio C Hamano wrote:\n> Fedor Sergeev <Fedor.Sergeev@Sun.COM> writes:\n> \n> > I have recently hit a behavior which might well be a feature, \n> > but it was very surprising (in a bad sense) to me.\n> \n> It is a feature misfiring.\n> \n> Rebase is essentially a repeated cherry-pick, and a cherry-pick of commit\n\nBut cherry-pick does fail, as shown in my original mail!\n\n> A on top of commit B is done by a simplified 3-way merge between A and B\n> using the parent of A as the common ancestor.\n> \n>      A                          A'\n>     /                          /\n>    A^... pseudo history ...---B\n\nWell, my history is exactly that, not pseudo (and I dont quite follow your reasoning\nyet to understand whether this is important or not):\n\n   A   B\n    \\ /\n     A^\n\nA^ *is* a common ancestor of both A and B.\n\n> \n> When your history has renamed Makefile to Makefile2 (thereby losing\n> Makefile)\n\nMy history did not rename Makefile.\nThere were three identical Makefiles (in A^)\nAfter that one was deleted (in B).\nOn alternative branch it was edited (in A).\n\nIf I do *merge* A into B then it fails.\nIf I do *cherry-pick* A into B then it fails.\nIf I do *rebase* A onto B then it succeeds.\n\n> while transition from A^ to A modified Makefile, the difference\n> between A^ to A that is applied to B to produce A' contains only the\n> change about Makefile (and does not talk about the unchangedness of\n> Makefile1 nor Makefile2 --- in fact, when A' is created, the machinery\n> does not even know if A^ and A had Makefile1 or Makefile2).\n> \n> When applying the change to Makefile, it notices that B does not have\n> Makefile, but there is a path that is _identical_ to the preimage your\n> change applies to (namely, Makefile2).  To support people who rename\n> Makefile to Makefile2 in the history that led to B\n\nThere was no rename. There was a copy in initial commit (and you cant say if it\nwas Makefile copied into Makefile2 or vice verse).\nI dont believe it should really be called \"rename\", even if one of the copies was killed later.\n\n>, rebase (actually the\n> underlying \"am -3\" it calls is where this rename detection smart lies)\n> applies the changes to the \"renamed\" path.\n\nIn this given case both Makefile1 and Makefile2 were absolutely equal. \nIf rebase chose to edit Makefile2 why didnt it change Makefile1?\n\n> \n> You might be able to work this around by forcing rebase not to use the\n> simplified 3-way merge, by saying \"rebase -m\".\n\nYeah, it worked.\n...\nCONFLICT (delete/modify): Makefile deleted in master and modified in HEAD~0. Version HEAD~0 of Makefile left in tree.\n...\n\nThough it does make me wonder why *simplified* 3-way merge is smarter than git merge ;)))\n\nbest regards,\n  Fedor..\n"},{"id":"95415","messageId":"7v1vxj3zj9.fsf@gitster.siamese.dyndns.org","threadId":"16254","inReplyTo":"20081110233649.GI6799@sun.com","subject":"Re: overly smart rebase - bug or feature?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-11-10T23:57:14Z","receivedAt":"2008-11-10T23:57:14Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fedor Sergeev <Fedor.Sergeev@Sun.COM> writes:\n\n> On Mon, Nov 10, 2008 at 03:14:42PM -0800, Junio C Hamano wrote:\n>> Fedor Sergeev <Fedor.Sergeev@Sun.COM> writes:\n>> \n>> > I have recently hit a behavior which might well be a feature, \n>> > but it was very surprising (in a bad sense) to me.\n>> \n>> It is a feature misfiring.\n>> \n>> Rebase is essentially a repeated cherry-pick, and a cherry-pick of commit\n>\n> But cherry-pick does fail, as shown in my original mail!\n>\n>> A on top of commit B is done by a simplified 3-way merge between A and B\n>> using the parent of A as the common ancestor.\n>> \n>>      A                          A'\n>>     /                          /\n>>    A^... pseudo history ...---B\n>\n> Well, my history is exactly that, not pseudo (and I dont quite follow your reasoning\n> yet to understand whether this is important or not):\n>\n>    A   B\n>     \\ /\n>      A^\n>\n> A^ *is* a common ancestor of both A and B.\n>\n>> \n>> When your history has renamed Makefile to Makefile2 (thereby losing\n>> Makefile)\n>\n> My history did not rename Makefile.\n> There were three identical Makefiles (in A^)\n> After that one was deleted (in B).\n> On alternative branch it was edited (in A).\n>\n> If I do *merge* A into B then it fails.\n> If I do *cherry-pick* A into B then it fails.\n> If I do *rebase* A onto B then it succeeds.\n>\n>> while transition from A^ to A modified Makefile, the difference\n>> between A^ to A that is applied to B to produce A' contains only the\n>> change about Makefile (and does not talk about the unchangedness of\n>> Makefile1 nor Makefile2 --- in fact, when A' is created, the machinery\n>> does not even know if A^ and A had Makefile1 or Makefile2).\n>> \n>> When applying the change to Makefile, it notices that B does not have\n>> Makefile, but there is a path that is _identical_ to the preimage your\n>> change applies to (namely, Makefile2).  To support people who rename\n>> Makefile to Makefile2 in the history that led to B\n>\n> There was no rename. There was a copy in initial commit (and you cant say if it\n> was Makefile copied into Makefile2 or vice verse).\n> I dont believe it should really be called \"rename\", even if one of the copies was killed later.\n>\n>>, rebase (actually the\n>> underlying \"am -3\" it calls is where this rename detection smart lies)\n>> applies the changes to the \"renamed\" path.\n>\n> In this given case both Makefile1 and Makefile2 were absolutely equal. \n> If rebase chose to edit Makefile2 why didnt it change Makefile1?\n>\n>> \n>> You might be able to work this around by forcing rebase not to use the\n>> simplified 3-way merge, by saying \"rebase -m\".\n>\n> Yeah, it worked.\n> ...\n> CONFLICT (delete/modify): Makefile deleted in master and modified in HEAD~0. Version HEAD~0 of Makefile left in tree.\n> ...\n>\n> Though it does make me wonder why *simplified* 3-way merge is smarter than git merge ;)))\n\nSimplified one is not _smarter_.  It is merely _faster_, exactly because\nit only looks at the paths between A^..A and nothing else.\n\nAnd that is why it cannot tell between the case where both A^ and A have\nMakefile2 or they both lack it.  And that is exactly why application of\nthis change on top of B is mistaken as a patch to a renamed path.  From\nB's point of view:\n\n - Incoming change says \"I changed Makefile from this shape to that\n   shape\", and nothing else;\n\n - B does not have Makefile, but it happens to have the contents at path\n   Makefile2 that is identical to the preimage of that incoming change.\n\nwhich makes it guess (when falling back to 3-way merge) that somewhere\nleading to B what used to be at Makefile (which is what the incoming\nchange claims to change) may have been renamed to Makefile2 (because B\ndoes not have Makefile but does have it).\n"},{"id":"95628","messageId":"20081112213920.GB5018@sun.com","threadId":"16254","inReplyTo":"7vod0n41i5.fsf@gitster.siamese.dyndns.org","subject":"Re: overly smart rebase - bug or feature?","fromName":"Fedor Sergeev","fromEmail":"fedor.sergeev@sun.com","sentAt":"2008-11-12T21:39:21Z","receivedAt":"2008-11-12T21:39:21Z","isPatch":false,"sender":{"key":"fedor.sergeev@sun.com","avatar":null},"body":"On Mon, Nov 10, 2008, Junio C Hamano wrote:\n> Fedor Sergeev <Fedor.Sergeev@Sun.COM> writes:\n> >> You might be able to work this around by forcing rebase not to use the\n> >> simplified 3-way merge, by saying \"rebase -m\".\n> >\n> > Yeah, it worked.\n> > ...\n> > CONFLICT (delete/modify): Makefile deleted in master and modified in HEAD~0. Version HEAD~0 of Makefile left in tree.\n> > ...\n> >\n> > Though it does make me wonder why *simplified* 3-way merge is smarter than git merge ;)))\n> \n> Simplified one is not _smarter_.  It is merely _faster_, exactly because\n> it only looks at the paths between A^..A and nothing else.\n\nI seem to start getting grasp on it.\nPlease, correct me if I'm wrong:\n  - by default rebase uses \"simplified\" merge, which (roughly speaking) \n    simply goes around patching parent with changes from either branches A and B\n\n  - rebase -m applies 'recursive' merge (default merge strategy) which is \n    kind of smarter and determines a conflict in my case\n\n  - literally the same happens when I do merge instead of rebase \n\n  - cherry-pick fails just because \"patch B\" can not apply to A and that is\n    literally why rebase started falling out to *some* merge first hand\n\nIf the above is true then can you, please, answer the following questions:\n  - is there any merge strategy that can do \"simplified\" merge just like that in rebase?\n    (not that I need it, but just for educational purpose)\n\n  - does rebase perform simplified merge only because of speed considerations?\n    (e.g. are there any correctness/usability issues with using smarter merge algo on rebase) \n\n  - is there any .git/config variable that affects which merge to use upon rebase?\n\nbest regards,\n  Fedor.\n"},{"id":"95630","messageId":"7v63msmwi4.fsf@gitster.siamese.dyndns.org","threadId":"16254","inReplyTo":"20081112213920.GB5018@sun.com","subject":"Re: overly smart rebase - bug or feature?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-11-12T22:04:35Z","receivedAt":"2008-11-12T22:04:35Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fedor Sergeev <Fedor.Sergeev@Sun.COM> writes:\n\n> Please, correct me if I'm wrong:\n>\n>   - by default rebase uses \"simplified\" merge, which (roughly speaking) \n>     simply goes around patching parent with changes from either branches A and B\n>\n>   - rebase -m applies 'recursive' merge (default merge strategy) which is \n>     kind of smarter and determines a conflict in my case\n>\n>   - literally the same happens when I do merge instead of rebase \n\nIf \"the same\" means \"always use 'recursive' merge, without 'am -3'\n(mis)behaviour seen in rebase\", then yes.\n\n>   - cherry-pick fails just because \"patch B\" can not apply to A and that is\n>     literally why rebase started falling out to *some* merge first hand\n\nI do not know about this part.  Rebase _conceptually_ does cherry-pick but\nuses a different implementation.\n\n> If the above is true then can you, please, answer the following questions:\n\nI'll answer the one that cannot be answered without knowing history.  I\nsuspect answers to your other questions are found in the doc set.\n\n>   - does rebase perform simplified merge only because of speed considerations?\n\nHistorical accident.  Originally rebase was only \"format-patch | am\",\ni.e. lift a patch from the commits to be rebased, apply them in order.\n\nLater, \"am -3\" was invented that allows you to apply patches with fuzz by\nusing 3-way merge at the content level, which was successfull and rebase\nwas taught about using it.\n"}]}