{"thread":{"id":"30255","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","startedAt":"2012-04-16T06:26:41Z","lastAt":"2012-04-19T05:19:55Z","messageCount":22,"participants":["Michal Kiedrowicz","Linus Torvalds","Junio C Hamano","Michał Kiedrowicz"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"189369","messageId":"20120416082641.5d239ef6@mkiedrowicz.ivo.pl","threadId":"30255","inReplyTo":null,"subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Michal Kiedrowicz","fromEmail":"michal.kiedrowicz@gmail.com","sentAt":"2012-04-16T06:26:41Z","receivedAt":"2012-04-16T06:26:41Z","isPatch":false,"sender":{"key":"michal.kiedrowicz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/14072847?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n\n> * lt/octopus-simplify (2012-04-12) 1 commit\n>  - Make 'git merge' simplify parents earlier\n>\n> Octopus merge strategy did not reduce heads that are recorded in the\n> final commit.  This was done off-list.\n\nHeh, this seems to fix the issue I reported in [1], except... it\ndoesn't work for the testcase I posted :).  The problem is that this\ncommit makes Git 'fast-forward' to the first commit from remoteheads,\nnot from the reduced heads. See:\n\n\n\t$ git init /tmp/merge\n\tInitialized empty Git repository in /tmp/merge/.git/\n\t$ cd /tmp/merge\n\t$ echo a>>a && git add a && git commit -m first\n\t[master (root-commit) 7422615] first\n\t 1 file changed, 1 insertion(+)\n\t create mode 100644 a\n\t$ echo a>>a && git add a && git commit -m second\n\t[master 0ba02e5] second\n\t 1 file changed, 1 insertion(+)\n\t$ echo a>>a && git add a && git commit -m third\n\t[master 9bd11ac] third\n\t 1 file changed, 1 insertion(+)\n\t$ git checkout master~2\n\tHEAD is now at 7422615... first\n\t# This is OK:\n\t$ git merge master master~1 \n\tUpdating 7422615..9bd11ac\n\tFast-forward\n\t a |    2 ++\n\t 1 file changed, 2 insertions(+)\n\t$ git checkout master~2\n\tPrevious HEAD position was 9bd11ac... third\n\tHEAD is now at 7422615... first\n\t# This is not OK:\n\t$ git merge master~1 master \n\tUpdating 7422615..0ba02e5\n\tFast-forward\n\t a |    1 +\n\t 1 file changed, 1 insertion(+)\n\nThs following patch fixes that.\n\n[1] http://permalink.gmane.org/gmane.comp.version-control.git/190625\n\n\nSigned-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>\n(if this fix need a signed-off)\n---\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex f5947b9..075c99b 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1388,13 +1388,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n                if (verbosity >= 0)\n                        printf(_(\"Updating %s..%s\\n\"),\n                                hex,\n-                               find_unique_abbrev(remoteheads->item->object.sha1,\n+                               find_unique_abbrev(parents->item->object.sha1,\n                                DEFAULT_ABBREV));\n                strbuf_addstr(&msg, \"Fast-forward\");\n                if (have_message)\n                        strbuf_addstr(&msg,\n                                \" (no commit created; -m option ignored)\");\n-               commit = remoteheads->item;\n+               commit = parents->item;\n                if (!commit) {\n                        ret = 1;\n                        goto done;\n"},{"id":"189410","messageId":"CA+55aFwkf2bOLmUCU+_pSg0OzGyfQ1x-Cy_CiczpJN3zsThNWg@mail.gmail.com","threadId":"30255","inReplyTo":"20120416082641.5d239ef6@mkiedrowicz.ivo.pl","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2012-04-16T14:57:59Z","receivedAt":"2012-04-16T14:57:59Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"On Sun, Apr 15, 2012 at 11:26 PM, Michal Kiedrowicz\n<michal.kiedrowicz@gmail.com> wrote:\n>\n> Heh, this seems to fix the issue I reported in [1], except... it\n> doesn't work for the testcase I posted :).  The problem is that this\n> commit makes Git 'fast-forward' to the first commit from remoteheads,\n> not from the reduced heads.\n\nAck, good catch.\n\nThinking some more about this thing, I think we have a similar issue\nwith the \"Already up-to-date.\" thing.\n\nIt too had the \"only one remote-head\" test, which is wrong - what if\nyou try to do a octopus merge with *two* commits that are different,\nand are both in the past? It will fail the \"Already up-to-date\" test,\nand then do a \"fast-forward\" to the first remote parent, if I read the\ncode right..\n\nSo I think the \"Already up-to-date\" case should also be fixed, and in\nfact it becomes much more natural now that we have finalized the\nparents: we just check whether the one remaining parent is the same as\nHEAD.\n\nSo  Ack on Michal's patch, but I think we also should do the appended\nequivalent thing for the fast-forward test on top of it (it's\nwhite-space damaged, sorry).\n\nTrivially tested with\n\n    git merge HEAD^ HEAD^^\n\nwhich did the wrong thing before, and now works.\n\n                       Linus\n\n---\n builtin/merge.c |    3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 016a4dbee3b5..28fb5c9d6ada 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1370,8 +1370,7 @@ int cmd_merge(int argc, const char **argv, const\nchar *prefix)\n\n        if (!common)\n                ; /* No common ancestors found. We need a real merge. */\n-       else if (!remoteheads->next && !common->next &&\n-                       common->item == remoteheads->item) {\n+       else if (!parents->next && parents->item == head_commit) {\n                /*\n                 * If head can reach all the merge then we are up to date.\n                 * but first the most common case of merging one remote.\n"},{"id":"189438","messageId":"7vehrnwpoi.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"CA+55aFwkf2bOLmUCU+_pSg0OzGyfQ1x-Cy_CiczpJN3zsThNWg@mail.gmail.com","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-16T17:29:33Z","receivedAt":"2012-04-16T17:29:33Z","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> Trivially tested with\n>\n>     git merge HEAD^ HEAD^^\n>\n> which did the wrong thing before, and now works.\n>\n>                        Linus\n>\n> ---\n>  builtin/merge.c |    3 +--\n>  1 file changed, 1 insertion(+), 2 deletions(-)\n>\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index 016a4dbee3b5..28fb5c9d6ada 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -1370,8 +1370,7 @@ int cmd_merge(int argc, const char **argv, const\n> char *prefix)\n>\n>         if (!common)\n>                 ; /* No common ancestors found. We need a real merge. */\n> -       else if (!remoteheads->next && !common->next &&\n> -                       common->item == remoteheads->item) {\n> +       else if (!parents->next && parents->item == head_commit) {\n\nWhen everybody in remote_heads is an ancestor of the current HEAD,\nfinalize_parents() would have reduced parents to a single element list\nwith HEAD on it, and we are \"already up-to-date\".  Ok.\n\nI wonder if use of remoteheads later in the same function are correct,\nthough.  We equate \"!remoteheads->next\" and \"We are not doing octopus\",\nfor example.\n"},{"id":"189439","messageId":"7v7gxfwpc8.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"CA+55aFwkf2bOLmUCU+_pSg0OzGyfQ1x-Cy_CiczpJN3zsThNWg@mail.gmail.com","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-16T17:36:55Z","receivedAt":"2012-04-16T17:36:55Z","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> So  Ack on Michal's patch, but I think we also should do the appended\n> equivalent thing for the fast-forward test on top of it (it's\n> white-space damaged, sorry).\n>\n> Trivially tested with\n>\n>     git merge HEAD^ HEAD^^\n>\n> which did the wrong thing before, and now works.\n\nAnd it seems to break 6028 (\"merge -s ours\" and \"merge -s subtree\"\nup-to-date) X-<....\n"},{"id":"189441","messageId":"CA+55aFyZYnAxJc8YT=GEnq-fdXbHCXyx937KNBngn69Zuxmcgg@mail.gmail.com","threadId":"30255","inReplyTo":"7vehrnwpoi.fsf@alter.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2012-04-16T17:50:55Z","receivedAt":"2012-04-16T17:50:55Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"On Mon, Apr 16, 2012 at 10:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> I wonder if use of remoteheads later in the same function are correct,\n> though.  We equate \"!remoteheads->next\" and \"We are not doing octopus\",\n> for example.\n\nI do think it would generally be a great idea to never use\n\"remoteheads\" at all. An octopus merge that has been simplified to\njust two parents isn't really an octopus merge any more.\n\nSo I think you're probably right - we should try to avoid using\nremoteheads entirely, and any use is suspect\n\n                 Linus\n"},{"id":"189444","messageId":"CA+55aFwPrjtAttsH75tTGHO=14g_2GbyOtUt4eY9LPHbtcXvcQ@mail.gmail.com","threadId":"30255","inReplyTo":"7v7gxfwpc8.fsf@alter.siamese.dyndns.org","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2012-04-16T18:02:32Z","receivedAt":"2012-04-16T18:02:32Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"On Mon, Apr 16, 2012 at 10:36 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> And it seems to break 6028 (\"merge -s ours\" and \"merge -s subtree\"\n> up-to-date) X-<....\n\nUgh. I don't even get that far - I get to t3410, which breaks.\n\n(This is with both Michal's and my patch applied)\n\nOddly, running that test in verbose mode seems to imply that it's the\n*rebase* that succeeds, not the merges in that test. Maybe I'm reading\nthe test results wrong, I didn't really try to understand the test\nitself ;(\n\n                        Linus\n"},{"id":"189445","messageId":"CA+55aFyAsF4jNvNMKC6divzAfyVmgrHvxJtnX0fjkpp_bLHkPQ@mail.gmail.com","threadId":"30255","inReplyTo":"CA+55aFwPrjtAttsH75tTGHO=14g_2GbyOtUt4eY9LPHbtcXvcQ@mail.gmail.com","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2012-04-16T18:33:54Z","receivedAt":"2012-04-16T18:33:54Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"On Mon, Apr 16, 2012 at 11:02 AM, Linus Torvalds\n<torvalds@linux-foundation.org> wrote:\n>\n> Oddly, running that test in verbose mode seems to imply that it's the\n> *rebase* that succeeds, not the merges in that test. Maybe I'm reading\n> the test results wrong, I didn't really try to understand the test\n> itself ;(\n\nYes, it's the rebase that succeeds. \"git log -g\" in the trash\ndirectory shows that we ended up successfully rebasing J2:\n\n  commit 5fc34ec1a8ed96664198fefc74121cd052b10861\n  Reflog: HEAD@{1} (C O Mitter <committer@example.com>)\n  Reflog message: rebase -i (pick): Merge made by the 'recursive' strategy.\n  Author: A U Thor <author@example.com>\n  Date:   Thu Apr 7 15:28:13 2005 -0700\n\n      J2\n\nwhile a successful test will fail that.\n\nHowever, I don't actually see what changed.\n\nOh - one thing to note is that the *patch* of that successful rebase\nis empty. That may be the big clue: we successfully finish the merge\nwithout noticing that it didn't change any state, and we should have\nfailed it as an empty commit. Hmm?\n\n                   Linus\n"},{"id":"189465","messageId":"20120416233218.54daa2f6@gmail.com","threadId":"30255","inReplyTo":"CA+55aFyAsF4jNvNMKC6divzAfyVmgrHvxJtnX0fjkpp_bLHkPQ@mail.gmail.com","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Michał Kiedrowicz","fromEmail":"michal.kiedrowicz@gmail.com","sentAt":"2012-04-16T21:32:18Z","receivedAt":"2012-04-16T21:32:18Z","isPatch":false,"sender":{"key":"michal.kiedrowicz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/14072847?v=4"},"body":"Linus Torvalds <torvalds@linux-foundation.org> wrote:\n\n> On Mon, Apr 16, 2012 at 11:02 AM, Linus Torvalds\n> <torvalds@linux-foundation.org> wrote:\n> >\n> > Oddly, running that test in verbose mode seems to imply that it's the\n> > *rebase* that succeeds, not the merges in that test. Maybe I'm reading\n> > the test results wrong, I didn't really try to understand the test\n> > itself ;(\n> \n> Yes, it's the rebase that succeeds. \"git log -g\" in the trash\n> directory shows that we ended up successfully rebasing J2:\n> \n>   commit 5fc34ec1a8ed96664198fefc74121cd052b10861\n>   Reflog: HEAD@{1} (C O Mitter <committer@example.com>)\n>   Reflog message: rebase -i (pick): Merge made by the 'recursive' strategy.\n>   Author: A U Thor <author@example.com>\n>   Date:   Thu Apr 7 15:28:13 2005 -0700\n> \n>       J2\n> \n> while a successful test will fail that.\n> \n> However, I don't actually see what changed.\n> \n> Oh - one thing to note is that the *patch* of that successful rebase\n> is empty. That may be the big clue: we successfully finish the merge\n> without noticing that it didn't change any state, and we should have\n> failed it as an empty commit. Hmm?\n> \n>                    Linus\n\nSo, the difference is that `git merge --no-ff HEAD^` used to work, now\nit doesn't because we reduce_heads() only if we allow fast-forward (and\neven though there is just one remote we merge with, parents contains\ntwo commits). So what about that trivial patch instead (discarding our\nprevious patches)?\n---\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 08e01e8..27e0026 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1346,6 +1346,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tallow_trivial = 0;\n \t}\n \n+\tremoteheads = reduce_heads(remoteheads);\n+\n \tif (!remoteheads->next)\n \t\tcommon = get_merge_bases(head_commit, remoteheads->item, 1);\n \telse {\n"},{"id":"189472","messageId":"7vwr5fs5a4.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"CA+55aFyZYnAxJc8YT=GEnq-fdXbHCXyx937KNBngn69Zuxmcgg@mail.gmail.com","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-16T22:03:47Z","receivedAt":"2012-04-16T22:03:47Z","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> On Mon, Apr 16, 2012 at 10:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> I wonder if use of remoteheads later in the same function are correct,\n>> though.  We equate \"!remoteheads->next\" and \"We are not doing octopus\",\n>> for example.\n>\n> I do think it would generally be a great idea to never use\n> \"remoteheads\" at all. An octopus merge that has been simplified to\n> just two parents isn't really an octopus merge any more.\n>\n> So I think you're probably right - we should try to avoid using\n> remoteheads entirely, and any use is suspect\n\nI am still looking at the codepaths involved.  It looks feasible, but\nwould affect quite a lot of them to deal with many corner cases, I am\nafraid.\n\nOne of the worst is the \"traditional merge format\" where format-merge-msg\nis called outside \"git merge\" to prepare the merge message created by \"git\npull\".  We haven't reduced heads at that stage yet, so the message fed to\nus will list what happened on the redundant branches.\n"},{"id":"189498","messageId":"CA+55aFzGwPYNn2baFhEr4msBTV7__nkTSUqAZ7=PRVoYrchV5w@mail.gmail.com","threadId":"30255","inReplyTo":"20120416233218.54daa2f6@gmail.com","subject":"Re: What's cooking in git.git (Apr 2012, #05; Thu, 12)","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2012-04-17T01:22:47Z","receivedAt":"2012-04-17T01:22:47Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"2012/4/16 Michał Kiedrowicz <michal.kiedrowicz@gmail.com>:\n>\n> So, the difference is that `git merge --no-ff HEAD^` used to work, now\n> it doesn't because we reduce_heads() only if we allow fast-forward (and\n> even though there is just one remote we merge with, parents contains\n> two commits). So what about that trivial patch instead (discarding our\n> previous patches)?\n\nYes, I suspect this might work.\n\nAnd I like how reducing the remoteheads should also automatically fix\nthe case of the commit messages containing redundant information when\nyou give the same branch multiple times (assuming you have the whole\nmerge log thing enabled).\n\n                     Linus\n"},{"id":"189550","messageId":"1334687118-5386-1-git-send-email-michal.kiedrowicz@gmail.com","threadId":"30255","inReplyTo":"CA+55aFzGwPYNn2baFhEr4msBTV7__nkTSUqAZ7=PRVoYrchV5w@mail.gmail.com","subject":"[PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Michał Kiedrowicz","fromEmail":"michal.kiedrowicz@gmail.com","sentAt":"2012-04-17T18:25:18Z","receivedAt":"2012-04-17T18:25:18Z","isPatch":true,"sender":{"key":"michal.kiedrowicz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/14072847?v=4"},"body":"This makes us do proper fast-forward merges even for octopus merges,\nwhich could otherwise result in \"merge commit\" that only had one actual\nparent, and should have been a fast-forward.\n\nOdd-case-triggered-by: James Morris <jmorris@namei.org>\nSigned-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>\n---\n\nThis is the proposed fix sent as a proper patch with commit message\nstolen from Linus and testcases.\n\nI'm not sure if I don't introduce a memleak with the call to\nreduce_heads() but other callers seem to not care, just like whole\ncmd_merge().\n\n builtin/merge.c               |    3 +++\n t/t7603-merge-reduce-heads.sh |   19 +++++++++++++++++++\n 2 files changed, 22 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 08e01e8..2d5930f 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1346,6 +1346,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tallow_trivial = 0;\n \t}\n \n+\tif (remoteheads->next)\n+\t\tremoteheads = reduce_heads(remoteheads);\n+\n \tif (!remoteheads->next)\n \t\tcommon = get_merge_bases(head_commit, remoteheads->item, 1);\n \telse {\ndiff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh\nindex 7e17eb4..a3b08a6 100755\n--- a/t/t7603-merge-reduce-heads.sh\n+++ b/t/t7603-merge-reduce-heads.sh\n@@ -113,4 +113,23 @@ test_expect_success 'verify merge result' '\n \ttest $(git rev-parse HEAD^1) = $(git rev-parse E2) &&\n \ttest $(git rev-parse HEAD^2) = $(git rev-parse I2)\n '\n+\n+test_expect_success 'fast-forward to redundant refs' '\n+\tgit reset --hard c0 &&\n+\tgit merge c4 c5\n+'\n+\n+test_expect_success 'verify merge result' '\n+\ttest $(git rev-parse HEAD) = $(git rev-parse c5)\n+'\n+\n+test_expect_success 'merge up-to-date redundant refs' '\n+\tgit reset --hard c5 &&\n+\tgit merge c0 c4\n+'\n+\n+test_expect_success 'verify merge result' '\n+\ttest $(git rev-parse HEAD) = $(git rev-parse c5)\n+'\n+\n test_done\n-- \n1.7.8.4\n"},{"id":"189557","messageId":"7vzkaambre.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"1334687118-5386-1-git-send-email-michal.kiedrowicz@gmail.com","subject":"Re: [PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T18:52:37Z","receivedAt":"2012-04-17T18:52:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michał Kiedrowicz <michal.kiedrowicz@gmail.com> writes:\n\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index 08e01e8..2d5930f 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -1346,6 +1346,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n>  \t\t\tallow_trivial = 0;\n>  \t}\n>  \n> +\tif (remoteheads->next)\n> +\t\tremoteheads = reduce_heads(remoteheads);\n> +\n\nIf your current HEAD is an ancestor of one of the commit on that list, the\nabove does not omit it from the parent list of the resulting merge commit,\nbut if you performed the same merge while on one of the commit being\nmerged, your current HEAD will be excluded with reduce_heads(), which\nwould mean that you will end up recording a different history even though\na merge is supposed to be symmetrical.\n\nIn other words, isn't any solution that calls reduce_heads() only on\nremoteheads fundamentally wrong and merely papering over the problem?\n"},{"id":"189560","messageId":"CA+55aFzLFA535CtjLJe1p62H=nunQ=vrL_mPYsYJB0e8U7mpdg@mail.gmail.com","threadId":"30255","inReplyTo":"7vzkaambre.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2012-04-17T20:09:09Z","receivedAt":"2012-04-17T20:09:09Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"2012/4/17 Junio C Hamano <gitster@pobox.com>:\n>\n> If your current HEAD is an ancestor of one of the commit on that list, the\n> above does not omit it from the parent list of the resulting merge commit,\n> but if you performed the same merge while on one of the commit being\n> merged, your current HEAD will be excluded with reduce_heads(), which\n> would mean that you will end up recording a different history even though\n> a merge is supposed to be symmetrical.\n>\n> In other words, isn't any solution that calls reduce_heads() only on\n> remoteheads fundamentally wrong and merely papering over the problem?\n\nI think Michał's patch, together with my original one (but not the\nfixups later) is actually the right thing to do.\n\nMichał's patch fixes the \"log shown multiple times\" problem. It also\nturns a certain class of octopus merges into trivial common merges,\nwhich is good.\n\nSo I'd suggest:\n - undo the two top commits from lt/octopus-simplify\n - apply Michał's patch on top of the remaining one commit\n\nIt's not perfect, and I really think we could simplify things a bit\nmore here, but I think the two commits together fix the problems in\npractice.\n\nHmm?\n\n                     Linus\n"},{"id":"189562","messageId":"1334694886-27756-1-git-send-email-gitster@pobox.com","threadId":"30255","inReplyTo":"CA+55aFyZYnAxJc8YT=GEnq-fdXbHCXyx937KNBngn69Zuxmcgg@mail.gmail.com","subject":"[PATCH 0/4] merge: reduce set of parents consistently","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T20:34:42Z","receivedAt":"2012-04-17T20:34:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"So this is my attempt to address \"Eek, why does your Octopus record many\nirrelevant parents?!?!?!\" issue from Linus and Michał (but without reusing\nany code from them, except for the initial test).\n\nIn the original code, the variable remoteheads held the list of parents\nread from the command line, and it was used throughout the code to see how\nmany parents the resulting merge will have (used to see if we use twohead\nor octopus strategy), except that right before we come up with the set of\nparents to record in the resulting merge commit, we discarded redundant\nones.\n\nThe updated code instead reduces the parents right after we read them.\nThis even detects the case where our HEAD is an ancestor of one of the\ncommits being merged, in which case HEAD will not be recorded unless we\nare deliberately recording a fast-forward case as a real merge.\n\nJunio C Hamano (3):\n  builtin/merge.c: remove \"remoteheads\" global variable\n  builtin/merge.c: collect other parents early\n  builtin/merge.c: reduce parents early\n\nMichał Kiedrowicz (1):\n  git-merge: test octopus with redundant parents\n\n builtin/merge.c               |  142 +++++++++++++++++++++++++----------------\n t/t6028-merge-up-to-date.sh   |   17 ++++-\n t/t7602-merge-octopus-many.sh |   10 ++-\n 3 files changed, 108 insertions(+), 61 deletions(-)\n\n-- \n1.7.10.332.g1863c\n"},{"id":"189563","messageId":"1334694886-27756-2-git-send-email-gitster@pobox.com","threadId":"30255","inReplyTo":"1334694886-27756-1-git-send-email-gitster@pobox.com","subject":"[PATCH 1/4] git-merge: test octopus with redundant parents","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T20:34:43Z","receivedAt":"2012-04-17T20:34:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>\n\nThis happens when git merge is run to merge multiple commits that are\ndescendants of current HEAD (or are HEAD).  We've hit this while updating\nmaster to origin/master but accidentaly we called (while being on master):\n\n\t$ git merge master origin/master\n\nHere is a minimal testcase:\n\n\t$ git init a && cd a\n\t$ echo a >a && git add a\n\t$ git commit -minitial\n\t$ echo b >a && git add a\n\t$ git commit -msecond\n\t$ git checkout master^\n\n\t$ git merge master master\n\tFast-forwarding to: master\n\tAlready up-to-date with master\n\tMerge made by the 'octopus' strategy.\n\t a |    2 +-\n\t  1 files changed, 1 insertions(+), 1 deletions(-)\n\n\t$ git cat-file commit HEAD\n\ttree eebfed94e75e7760540d1485c740902590a00332\n\tparent bd679e85202280b263e20a57639a142fa14c2c64\n\tauthor Michał Kiedrowicz <michal.kiedrowicz@gmail.com> 1329132996 +0100\n\tcommitter Michał Kiedrowicz <michal.kiedrowicz@gmail.com> 1329132996 +0100\n\n\tMerge branches 'master' and 'master' into HEAD\n\nSigned-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t6028-merge-up-to-date.sh |   17 ++++++++++++++++-\n 1 file changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh\nindex a91644e..824fca5 100755\n--- a/t/t6028-merge-up-to-date.sh\n+++ b/t/t6028-merge-up-to-date.sh\n@@ -16,7 +16,12 @@ test_expect_success setup '\n \ttest_tick &&\n \tgit commit -m second &&\n \tgit tag c1 &&\n-\tgit branch test\n+\tgit branch test &&\n+\techo third >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m third &&\n+\tgit tag c2\n '\n \n test_expect_success 'merge -s recursive up-to-date' '\n@@ -74,4 +79,14 @@ test_expect_success 'merge -s subtree up-to-date' '\n \n '\n \n+test_expect_failure 'merge fast-forward octopus' '\n+\n+\tgit reset --hard c0 &&\n+\ttest_tick &&\n+\tgit merge c1 c2\n+\texpect=$(git rev-parse c2) &&\n+\tcurrent=$(git rev-parse HEAD) &&\n+\ttest \"$expect\" = \"$current\"\n+'\n+\n test_done\n-- \n1.7.10.332.g1863c\n"},{"id":"189564","messageId":"1334694886-27756-3-git-send-email-gitster@pobox.com","threadId":"30255","inReplyTo":"1334694886-27756-1-git-send-email-gitster@pobox.com","subject":"[PATCH 2/4] builtin/merge.c: remove \"remoteheads\" global variable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T20:34:44Z","receivedAt":"2012-04-17T20:34:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Instead pass it around starting from the toplevel cmd_merge()\nas an explicit parameter.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/merge.c |   51 ++++++++++++++++++++++++++++-----------------------\n 1 file changed, 28 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 5126443..c5ca70b 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -52,7 +52,6 @@ static int fast_forward_only, option_edit = -1;\n static int allow_trivial = 1, have_message;\n static int overwrite_ignore = 1;\n static struct strbuf merge_msg = STRBUF_INIT;\n-static struct commit_list *remoteheads;\n static struct strategy **use_strategies;\n static size_t use_strategies_nr, use_strategies_alloc;\n static const char **xopts;\n@@ -318,7 +317,7 @@ static void finish_up_to_date(const char *msg)\n \tdrop_save();\n }\n \n-static void squash_message(struct commit *commit)\n+static void squash_message(struct commit *commit, struct commit_list *remoteheads)\n {\n \tstruct rev_info rev;\n \tstruct strbuf out = STRBUF_INIT;\n@@ -366,6 +365,7 @@ static void squash_message(struct commit *commit)\n }\n \n static void finish(struct commit *head_commit,\n+\t\t   struct commit_list *remoteheads,\n \t\t   const unsigned char *new_head, const char *msg)\n {\n \tstruct strbuf reflog_message = STRBUF_INIT;\n@@ -380,7 +380,7 @@ static void finish(struct commit *head_commit,\n \t\t\tgetenv(\"GIT_REFLOG_ACTION\"), msg);\n \t}\n \tif (squash) {\n-\t\tsquash_message(head_commit);\n+\t\tsquash_message(head_commit, remoteheads);\n \t} else {\n \t\tif (verbosity >= 0 && !merge_msg.len)\n \t\t\tprintf(_(\"No merge message -- not updating HEAD\\n\"));\n@@ -681,6 +681,7 @@ int try_merge_command(const char *strategy, size_t xopts_nr,\n }\n \n static int try_merge_strategy(const char *strategy, struct commit_list *common,\n+\t\t\t      struct commit_list *remoteheads,\n \t\t\t      struct commit *head, const char *head_arg)\n {\n \tint index_fd;\n@@ -874,14 +875,14 @@ static void read_merge_msg(struct strbuf *msg)\n \t\tdie_errno(_(\"Could not read from '%s'\"), filename);\n }\n \n-static void write_merge_state(void);\n-static void abort_commit(const char *err_msg)\n+static void write_merge_state(struct commit_list *);\n+static void abort_commit(struct commit_list *remoteheads, const char *err_msg)\n {\n \tif (err_msg)\n \t\terror(\"%s\", err_msg);\n \tfprintf(stderr,\n \t\t_(\"Not committing merge; use 'git commit' to complete the merge.\\n\"));\n-\twrite_merge_state();\n+\twrite_merge_state(remoteheads);\n \texit(1);\n }\n \n@@ -892,7 +893,7 @@ N_(\"Please enter a commit message to explain why this merge is necessary,\\n\"\n    \"Lines starting with '#' will be ignored, and an empty message aborts\\n\"\n    \"the commit.\\n\");\n \n-static void prepare_to_commit(void)\n+static void prepare_to_commit(struct commit_list *remoteheads)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *comment = _(merge_editor_comment);\n@@ -905,18 +906,18 @@ static void prepare_to_commit(void)\n \t\t git_path(\"MERGE_MSG\"), \"merge\", NULL, NULL);\n \tif (option_edit) {\n \t\tif (launch_editor(git_path(\"MERGE_MSG\"), NULL, NULL))\n-\t\t\tabort_commit(NULL);\n+\t\t\tabort_commit(remoteheads, NULL);\n \t}\n \tread_merge_msg(&msg);\n \tstripspace(&msg, option_edit);\n \tif (!msg.len)\n-\t\tabort_commit(_(\"Empty commit message.\"));\n+\t\tabort_commit(remoteheads, _(\"Empty commit message.\"));\n \tstrbuf_release(&merge_msg);\n \tstrbuf_addbuf(&merge_msg, &msg);\n \tstrbuf_release(&msg);\n }\n \n-static int merge_trivial(struct commit *head)\n+static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n {\n \tunsigned char result_tree[20], result_commit[20];\n \tstruct commit_list *parent = xmalloc(sizeof(*parent));\n@@ -927,17 +928,18 @@ static int merge_trivial(struct commit *head)\n \tparent->next = xmalloc(sizeof(*parent->next));\n \tparent->next->item = remoteheads->item;\n \tparent->next->next = NULL;\n-\tprepare_to_commit();\n+\tprepare_to_commit(remoteheads);\n \tif (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,\n \t\t\tsign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n-\tfinish(head, result_commit, \"In-index merge\");\n+\tfinish(head, remoteheads, result_commit, \"In-index merge\");\n \tdrop_save();\n \treturn 0;\n }\n \n static int finish_automerge(struct commit *head,\n \t\t\t    struct commit_list *common,\n+\t\t\t    struct commit_list *remoteheads,\n \t\t\t    unsigned char *result_tree,\n \t\t\t    const char *wt_strategy)\n {\n@@ -959,13 +961,13 @@ static int finish_automerge(struct commit *head,\n \t\t\tpptr = &commit_list_insert(j->item, pptr)->next;\n \t}\n \tstrbuf_addch(&merge_msg, '\\n');\n-\tprepare_to_commit();\n+\tprepare_to_commit(remoteheads);\n \tfree_commit_list(remoteheads);\n \tif (commit_tree(&merge_msg, result_tree, parents, result_commit,\n \t\t\tNULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n-\tfinish(head, result_commit, buf.buf);\n+\tfinish(head, remoteheads, result_commit, buf.buf);\n \tstrbuf_release(&buf);\n \tdrop_save();\n \treturn 0;\n@@ -1070,7 +1072,7 @@ static int setup_with_upstream(const char ***argv)\n \treturn i;\n }\n \n-static void write_merge_state(void)\n+static void write_merge_state(struct commit_list *remoteheads)\n {\n \tconst char *filename;\n \tint fd;\n@@ -1148,6 +1150,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tint best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;\n \tstruct commit_list *common = NULL;\n \tconst char *best_strategy = NULL, *wt_strategy = NULL;\n+\tstruct commit_list *remoteheads = NULL;\n \tstruct commit_list **remotes = &remoteheads;\n \tvoid *branch_to_free;\n \n@@ -1400,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tgoto done;\n \t\t}\n \n-\t\tfinish(head_commit, commit->object.sha1, msg.buf);\n+\t\tfinish(head_commit, remoteheads, commit->object.sha1, msg.buf);\n \t\tdrop_save();\n \t\tgoto done;\n \t} else if (!remoteheads->next && common->next)\n@@ -1422,7 +1425,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tif (!read_tree_trivial(common->item->object.sha1,\n \t\t\t\t\t       head_commit->object.sha1,\n \t\t\t\t\t       remoteheads->item->object.sha1)) {\n-\t\t\t\tret = merge_trivial(head_commit);\n+\t\t\t\tret = merge_trivial(head_commit, remoteheads);\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t\tprintf(_(\"Nope.\\n\"));\n@@ -1493,7 +1496,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\twt_strategy = use_strategies[i]->name;\n \n \t\tret = try_merge_strategy(use_strategies[i]->name,\n-\t\t\t\t\t common, head_commit, head_arg);\n+\t\t\t\t\t common, remoteheads,\n+\t\t\t\t\t head_commit, head_arg);\n \t\tif (!option_commit && !ret) {\n \t\t\tmerge_was_ok = 1;\n \t\t\t/*\n@@ -1535,8 +1539,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t * auto resolved the merge cleanly.\n \t */\n \tif (automerge_was_ok) {\n-\t\tret = finish_automerge(head_commit, common, result_tree,\n-\t\t\t\t       wt_strategy);\n+\t\tret = finish_automerge(head_commit, common, remoteheads,\n+\t\t\t\t       result_tree, wt_strategy);\n \t\tgoto done;\n \t}\n \n@@ -1561,13 +1565,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\trestore_state(head_commit->object.sha1, stash);\n \t\tprintf(_(\"Using the %s to prepare resolving by hand.\\n\"),\n \t\t\tbest_strategy);\n-\t\ttry_merge_strategy(best_strategy, common, head_commit, head_arg);\n+\t\ttry_merge_strategy(best_strategy, common, remoteheads,\n+\t\t\t\t   head_commit, head_arg);\n \t}\n \n \tif (squash)\n-\t\tfinish(head_commit, NULL, NULL);\n+\t\tfinish(head_commit, remoteheads, NULL, NULL);\n \telse\n-\t\twrite_merge_state();\n+\t\twrite_merge_state(remoteheads);\n \n \tif (merge_was_ok)\n \t\tfprintf(stderr, _(\"Automatic merge went well; \"\n-- \n1.7.10.332.g1863c\n"},{"id":"189565","messageId":"1334694886-27756-4-git-send-email-gitster@pobox.com","threadId":"30255","inReplyTo":"1334694886-27756-1-git-send-email-gitster@pobox.com","subject":"[PATCH 3/4] builtin/merge.c: collect other parents early","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T20:34:45Z","receivedAt":"2012-04-17T20:34:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Move the code around to populate remoteheads list early in the process\nbefore any decision regarding twohead vs octopus and fast-forwardness is\nmade.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/merge.c |   40 +++++++++++++++++++++++++++-------------\n 1 file changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex c5ca70b..2cef2f6 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1137,6 +1137,21 @@ static int default_edit_option(void)\n \t\tst_stdin.st_mode == st_stdout.st_mode);\n }\n \n+static struct commit_list *collect_parents(int argc, const char **argv)\n+{\n+\tint i;\n+\tstruct commit_list *remoteheads = NULL;\n+\tstruct commit_list **remotes = &remoteheads;\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tstruct commit *commit = get_merge_parent(argv[i]);\n+\t\tif (!commit)\n+\t\t\tdie(_(\"%s - not something we can merge\"), argv[i]);\n+\t\tremotes = &commit_list_insert(commit, remotes)->next;\n+\t}\n+\t*remotes = NULL;\n+\treturn remoteheads;\n+}\n \n int cmd_merge(int argc, const char **argv, const char *prefix)\n {\n@@ -1150,8 +1165,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tint best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;\n \tstruct commit_list *common = NULL;\n \tconst char *best_strategy = NULL, *wt_strategy = NULL;\n-\tstruct commit_list *remoteheads = NULL;\n-\tstruct commit_list **remotes = &remoteheads;\n+\tstruct commit_list *remoteheads, *p;\n \tvoid *branch_to_free;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n@@ -1256,6 +1270,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\thead_arg = argv[1];\n \t\targv += 2;\n \t\targc -= 2;\n+\t\tremoteheads = collect_parents(argc, argv);\n \t} else if (!head_commit) {\n \t\tstruct commit *remote_head;\n \t\t/*\n@@ -1271,7 +1286,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tif (!allow_fast_forward)\n \t\t\tdie(_(\"Non-fast-forward commit does not make sense into \"\n \t\t\t    \"an empty head\"));\n-\t\tremote_head = get_merge_parent(argv[0]);\n+\t\tremoteheads = collect_parents(argc, argv);\n+\t\tremote_head = remoteheads->item;\n \t\tif (!remote_head)\n \t\t\tdie(_(\"%s - not something we can merge\"), argv[0]);\n \t\tread_empty(remote_head->object.sha1, 0);\n@@ -1289,8 +1305,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t * the standard merge summary message to be appended\n \t\t * to the given message.\n \t\t */\n-\t\tfor (i = 0; i < argc; i++)\n-\t\t\tmerge_name(argv[i], &merge_names);\n+\t\tremoteheads = collect_parents(argc, argv);\n+\t\tfor (p = remoteheads; p; p = p->next)\n+\t\t\tmerge_name(merge_remote_util(p->item)->name, &merge_names);\n \n \t\tif (!have_message || shortlog_len) {\n \t\t\tstruct fmt_merge_msg_opts opts;\n@@ -1309,19 +1326,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tbuiltin_merge_options);\n \n \tstrbuf_addstr(&buf, \"merge\");\n-\tfor (i = 0; i < argc; i++)\n-\t\tstrbuf_addf(&buf, \" %s\", argv[i]);\n+\tfor (p = remoteheads; p; p = p->next)\n+\t\tstrbuf_addf(&buf, \" %s\", merge_remote_util(p->item)->name);\n \tsetenv(\"GIT_REFLOG_ACTION\", buf.buf, 0);\n \tstrbuf_reset(&buf);\n \n-\tfor (i = 0; i < argc; i++) {\n-\t\tstruct commit *commit = get_merge_parent(argv[i]);\n-\t\tif (!commit)\n-\t\t\tdie(_(\"%s - not something we can merge\"), argv[i]);\n-\t\tremotes = &commit_list_insert(commit, remotes)->next;\n+\tfor (p = remoteheads; p; p = p->next) {\n+\t\tstruct commit *commit = p->item;\n \t\tstrbuf_addf(&buf, \"GITHEAD_%s\",\n \t\t\t    sha1_to_hex(commit->object.sha1));\n-\t\tsetenv(buf.buf, argv[i], 1);\n+\t\tsetenv(buf.buf, merge_remote_util(commit)->name, 1);\n \t\tstrbuf_reset(&buf);\n \t\tif (!fast_forward_only &&\n \t\t    merge_remote_util(commit) &&\n-- \n1.7.10.332.g1863c\n"},{"id":"189566","messageId":"1334694886-27756-5-git-send-email-gitster@pobox.com","threadId":"30255","inReplyTo":"1334694886-27756-1-git-send-email-gitster@pobox.com","subject":"[PATCH 4/4] builtin/merge.c: reduce parents early","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T20:34:46Z","receivedAt":"2012-04-17T20:34:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Instead of waiting until we record the parents of resulting merge, reduce\nredundant parents (including our HEAD) immediately after reading them.\n\nThe change to t7602 illustrates the essence of the effect of this change.\nThe octopus merge strategy used to be fed with redundant commits only to\ndiscard them as \"up-to-date\", but we no longer feed such redundant commits\nto it and the affected test degenerates to a regular two-head merge.\n\nAnd obviously the known-to-be-broken test in t7602 is now fixed.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/merge.c               |   65 +++++++++++++++++++++++++----------------\n t/t6028-merge-up-to-date.sh   |    2 +-\n t/t7602-merge-octopus-many.sh |   10 +++----\n 3 files changed, 45 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 2cef2f6..20aeca0 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -938,31 +938,22 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n }\n \n static int finish_automerge(struct commit *head,\n+\t\t\t    int head_subsumed,\n \t\t\t    struct commit_list *common,\n \t\t\t    struct commit_list *remoteheads,\n \t\t\t    unsigned char *result_tree,\n \t\t\t    const char *wt_strategy)\n {\n-\tstruct commit_list *parents = NULL, *j;\n+\tstruct commit_list *parents = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tunsigned char result_commit[20];\n \n \tfree_commit_list(common);\n-\tif (allow_fast_forward) {\n-\t\tparents = remoteheads;\n+\tparents = remoteheads;\n+\tif (!head_subsumed || !allow_fast_forward)\n \t\tcommit_list_insert(head, &parents);\n-\t\tparents = reduce_heads(parents);\n-\t} else {\n-\t\tstruct commit_list **pptr = &parents;\n-\n-\t\tpptr = &commit_list_insert(head,\n-\t\t\t\tpptr)->next;\n-\t\tfor (j = remoteheads; j; j = j->next)\n-\t\t\tpptr = &commit_list_insert(j->item, pptr)->next;\n-\t}\n \tstrbuf_addch(&merge_msg, '\\n');\n \tprepare_to_commit(remoteheads);\n-\tfree_commit_list(remoteheads);\n \tif (commit_tree(&merge_msg, result_tree, parents, result_commit,\n \t\t\tNULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n@@ -1137,12 +1128,16 @@ static int default_edit_option(void)\n \t\tst_stdin.st_mode == st_stdout.st_mode);\n }\n \n-static struct commit_list *collect_parents(int argc, const char **argv)\n+static struct commit_list *collect_parents(struct commit *head_commit,\n+\t\t\t\t\t   int *head_subsumed,\n+\t\t\t\t\t   int argc, const char **argv)\n {\n \tint i;\n-\tstruct commit_list *remoteheads = NULL;\n+\tstruct commit_list *remoteheads = NULL, *parents, *next;\n \tstruct commit_list **remotes = &remoteheads;\n \n+\tif (head_commit)\n+\t\tremotes = &commit_list_insert(head_commit, remotes)->next;\n \tfor (i = 0; i < argc; i++) {\n \t\tstruct commit *commit = get_merge_parent(argv[i]);\n \t\tif (!commit)\n@@ -1150,6 +1145,20 @@ static struct commit_list *collect_parents(int argc, const char **argv)\n \t\tremotes = &commit_list_insert(commit, remotes)->next;\n \t}\n \t*remotes = NULL;\n+\n+\tparents = reduce_heads(remoteheads);\n+\n+\t*head_subsumed = 1; /* we will flip this to 0 when we find it */\n+\tfor (remoteheads = NULL, remotes = &remoteheads;\n+\t     parents;\n+\t     parents = next) {\n+\t\tstruct commit *commit = parents->item;\n+\t\tnext = parents->next;\n+\t\tif (commit == head_commit)\n+\t\t\t*head_subsumed = 0;\n+\t\telse\n+\t\t\tremotes = &commit_list_insert(commit, remotes)->next;\n+\t}\n \treturn remoteheads;\n }\n \n@@ -1161,7 +1170,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tstruct commit *head_commit;\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *head_arg;\n-\tint flag, i, ret = 0;\n+\tint flag, i, ret = 0, head_subsumed;\n \tint best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;\n \tstruct commit_list *common = NULL;\n \tconst char *best_strategy = NULL, *wt_strategy = NULL;\n@@ -1270,7 +1279,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\thead_arg = argv[1];\n \t\targv += 2;\n \t\targc -= 2;\n-\t\tremoteheads = collect_parents(argc, argv);\n+\t\tremoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);\n \t} else if (!head_commit) {\n \t\tstruct commit *remote_head;\n \t\t/*\n@@ -1286,7 +1295,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tif (!allow_fast_forward)\n \t\t\tdie(_(\"Non-fast-forward commit does not make sense into \"\n \t\t\t    \"an empty head\"));\n-\t\tremoteheads = collect_parents(argc, argv);\n+\t\tremoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);\n \t\tremote_head = remoteheads->item;\n \t\tif (!remote_head)\n \t\t\tdie(_(\"%s - not something we can merge\"), argv[0]);\n@@ -1305,7 +1314,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t * the standard merge summary message to be appended\n \t\t * to the given message.\n \t\t */\n-\t\tremoteheads = collect_parents(argc, argv);\n+\t\tremoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);\n \t\tfor (p = remoteheads; p; p = p->next)\n \t\t\tmerge_name(merge_remote_util(p->item)->name, &merge_names);\n \n@@ -1351,7 +1360,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\toption_edit = 0;\n \n \tif (!use_strategies) {\n-\t\tif (!remoteheads->next)\n+\t\tif (!remoteheads)\n+\t\t\t; /* already up-to-date */\n+\t\telse if (!remoteheads->next)\n \t\t\tadd_strategies(pull_twohead, DEFAULT_TWOHEAD);\n \t\telse\n \t\t\tadd_strategies(pull_octopus, DEFAULT_OCTOPUS);\n@@ -1364,7 +1375,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\tallow_trivial = 0;\n \t}\n \n-\tif (!remoteheads->next)\n+\tif (!remoteheads)\n+\t\t; /* already up-to-date */\n+\telse if (!remoteheads->next)\n \t\tcommon = get_merge_bases(head_commit, remoteheads->item, 1);\n \telse {\n \t\tstruct commit_list *list = remoteheads;\n@@ -1376,10 +1389,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tupdate_ref(\"updating ORIG_HEAD\", \"ORIG_HEAD\", head_commit->object.sha1,\n \t\t   NULL, 0, DIE_ON_ERR);\n \n-\tif (!common)\n+\tif (remoteheads && !common)\n \t\t; /* No common ancestors found. We need a real merge. */\n-\telse if (!remoteheads->next && !common->next &&\n-\t\t\tcommon->item == remoteheads->item) {\n+\telse if (!remoteheads ||\n+\t\t (!remoteheads->next && !common->next &&\n+\t\t  common->item == remoteheads->item)) {\n \t\t/*\n \t\t * If head can reach all the merge then we are up to date.\n \t\t * but first the most common case of merging one remote.\n@@ -1553,7 +1567,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t * auto resolved the merge cleanly.\n \t */\n \tif (automerge_was_ok) {\n-\t\tret = finish_automerge(head_commit, common, remoteheads,\n+\t\tret = finish_automerge(head_commit, head_subsumed,\n+\t\t\t\t       common, remoteheads,\n \t\t\t\t       result_tree, wt_strategy);\n \t\tgoto done;\n \t}\ndiff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh\nindex 824fca5..c518e9c 100755\n--- a/t/t6028-merge-up-to-date.sh\n+++ b/t/t6028-merge-up-to-date.sh\n@@ -79,7 +79,7 @@ test_expect_success 'merge -s subtree up-to-date' '\n \n '\n \n-test_expect_failure 'merge fast-forward octopus' '\n+test_expect_success 'merge fast-forward octopus' '\n \n \tgit reset --hard c0 &&\n \ttest_tick &&\ndiff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh\nindex 5783ebf..7117b57 100755\n--- a/t/t7602-merge-octopus-many.sh\n+++ b/t/t7602-merge-octopus-many.sh\n@@ -70,17 +70,15 @@ test_expect_success 'merge output uses pretty names' '\n '\n \n cat >expected <<\\EOF\n-Already up-to-date with c4\n-Trying simple merge with c5\n-Merge made by the 'octopus' strategy.\n+Merge made by the 'recursive' strategy.\n  c5.c |    1 +\n  1 file changed, 1 insertion(+)\n  create mode 100644 c5.c\n EOF\n \n-test_expect_success 'merge up-to-date output uses pretty names' '\n-\tgit merge c4 c5 >actual &&\n-\ttest_cmp actual expected\n+test_expect_success 'merge reduces irrelevant remote heads' '\n+\tGIT_MERGE_VERBOSITY=0 git merge c4 c5 >actual &&\n+\ttest_cmp expected actual\n '\n \n cat >expected <<\\EOF\n-- \n1.7.10.332.g1863c\n"},{"id":"189567","messageId":"7vvckym6ec.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"CA+55aFzLFA535CtjLJe1p62H=nunQ=vrL_mPYsYJB0e8U7mpdg@mail.gmail.com","subject":"Re: [PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-17T20:48:27Z","receivedAt":"2012-04-17T20:48:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@linux-foundation.org> writes:\n\n> 2012/4/17 Junio C Hamano <gitster@pobox.com>:\n>>\n>> If your current HEAD is an ancestor of one of the commit on that list, the\n>> above does not omit it from the parent list of the resulting merge commit,\n>> but if you performed the same merge while on one of the commit being\n>> merged, your current HEAD will be excluded with reduce_heads(), which\n>> would mean that you will end up recording a different history even though\n>> a merge is supposed to be symmetrical.\n>>\n>> In other words, isn't any solution that calls reduce_heads() only on\n>> remoteheads fundamentally wrong and merely papering over the problem?\n>\n> I think Michał's patch, together with my original one (but not the\n> fixups later) is actually the right thing to do.\n>\n> Michał's patch fixes the \"log shown multiple times\" problem. It also\n> turns a certain class of octopus merges into trivial common merges,\n> which is good.\n>\n> So I'd suggest:\n>  - undo the two top commits from lt/octopus-simplify\n>  - apply Michał's patch on top of the remaining one commit\n>\n> It's not perfect, and I really think we could simplify things a bit\n> more here, but I think the two commits together fix the problems in\n> practice.\n>\n> Hmm?\n\nI was cooking a fix on-and-off since yesterday evening, and sent it out a\nfew minutes ago. I think the spirit is almost the same as Michał's updated\npatch, but it reduces the heads even earlier to catch cases where Michał's\nupdated patch may misdiagnose arity of the resulting merge due to its use\nof remoteheads->next before the list is reduced (namely, the choice of the\ndefault strategy based on how many we are merging).\n"},{"id":"189637","messageId":"20120418201426.7070315f@gmail.com","threadId":"30255","inReplyTo":"7vvckym6ec.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Michał Kiedrowicz","fromEmail":"michal.kiedrowicz@gmail.com","sentAt":"2012-04-18T18:14:26Z","receivedAt":"2012-04-18T18:14:26Z","isPatch":true,"sender":{"key":"michal.kiedrowicz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/14072847?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n \n> I was cooking a fix on-and-off since yesterday evening, and sent it out a\n> few minutes ago. I think the spirit is almost the same as Michał's updated\n> patch, but it reduces the heads even earlier to catch cases where Michał's\n> updated patch may misdiagnose arity of the resulting merge due to its use\n> of remoteheads->next before the list is reduced (namely, the choice of the\n> default strategy based on how many we are merging).\n\nI like your patches, especially how reducing heads is now done in\ncollect_parents() instead of doing it twice (before merging and in\nfinish_automerge()).  And that you got rid of global remoteheads too.\n"},{"id":"189658","messageId":"7vzka8kd1m.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"20120418201426.7070315f@gmail.com","subject":"Re: [PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-18T20:20:05Z","receivedAt":"2012-04-18T20:20:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michał Kiedrowicz <michal.kiedrowicz@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> wrote:\n>  \n>> I was cooking a fix on-and-off since yesterday evening, and sent it out a\n>> few minutes ago. I think the spirit is almost the same as Michał's updated\n>> patch, but it reduces the heads even earlier to catch cases where Michał's\n>> updated patch may misdiagnose arity of the resulting merge due to its use\n>> of remoteheads->next before the list is reduced (namely, the choice of the\n>> default strategy based on how many we are merging).\n>\n> I like your patches, especially how reducing heads is now done in\n> collect_parents() instead of doing it twice (before merging and in\n> finish_automerge()).  And that you got rid of global remoteheads too.\n\nThanks.  Linus also said \"Yes, that sounds right (on the road with just my\nphone, sorry for the html)\" off-list to the series.\n"},{"id":"189679","messageId":"7vd374i9hg.fsf@alter.siamese.dyndns.org","threadId":"30255","inReplyTo":"7vzka8kd1m.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-merge: Reduce heads before trying to merge them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-19T05:19:55Z","receivedAt":"2012-04-19T05:19:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Michał Kiedrowicz <michal.kiedrowicz@gmail.com> writes:\n>\n>> Junio C Hamano <gitster@pobox.com> wrote:\n>>  \n>>> I was cooking a fix on-and-off since yesterday evening, and sent it out a\n>>> few minutes ago. I think the spirit is almost the same as Michał's updated\n>>> patch, but it reduces the heads even earlier to catch cases where Michał's\n>>> updated patch may misdiagnose arity of the resulting merge due to its use\n>>> of remoteheads->next before the list is reduced (namely, the choice of the\n>>> default strategy based on how many we are merging).\n>>\n>> I like your patches, especially how reducing heads is now done in\n>> collect_parents() instead of doing it twice (before merging and in\n>> finish_automerge()).  And that you got rid of global remoteheads too.\n>\n> Thanks.  Linus also said \"Yes, that sounds right (on the road with just my\n> phone, sorry for the html)\" off-list to the series.\n\nThere was another bit still missing from my 4-patch series.\n\n\"merge\" passes the updated test only because it runs the fmt_merge_msg()\ninternally after the reduction of the remotes, but without this update to\nbuiltin/fmt-merge.msg.c, \"pull\" doesn't and its merge summary lists\nunnecessary heads, because it calls the plumbing version from the script\nwith full set of possibly redundant parents.\n\n-- >8 --\nSubject: [PATCH] fmt-merge-msg: discard needless merge parents\n\nThis is used by \"git pull\" to construct a merge message from list of\nremote refs.  When pulling redundant set of refs, however, it did not\nfilter them even though the merge itself discards them as unnecessary.\n\nTeach the command to do the same for consistency.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/fmt-merge-msg.c       |  125 ++++++++++++++++++++++++++++++++++++++---\n t/t7603-merge-reduce-heads.sh |   31 +++++++++-\n 2 files changed, 146 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex c81a7fe..dcc12ea 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -53,7 +53,47 @@ static void init_src_data(struct src_data *data)\n static struct string_list srcs = STRING_LIST_INIT_DUP;\n static struct string_list origins = STRING_LIST_INIT_DUP;\n \n-static int handle_line(char *line)\n+struct merge_parents {\n+\tint alloc, nr;\n+\tstruct merge_parent {\n+\t\tunsigned char given[20];\n+\t\tunsigned char commit[20];\n+\t\tunsigned char used;\n+\t} *item;\n+};\n+\n+/*\n+ * I know, I know, this is inefficient, but you won't be pulling and merging\n+ * hundreds of heads at a time anyway.\n+ */\n+static struct merge_parent *find_merge_parent(struct merge_parents *table,\n+\t\t\t\t\t      unsigned char *given,\n+\t\t\t\t\t      unsigned char *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < table->nr; i++) {\n+\t\tif (given && hashcmp(table->item[i].given, given))\n+\t\t\tcontinue;\n+\t\tif (commit && hashcmp(table->item[i].commit, commit))\n+\t\t\tcontinue;\n+\t\treturn &table->item[i];\n+\t}\n+\treturn NULL;\n+}\n+\n+static void add_merge_parent(struct merge_parents *table,\n+\t\t\t     unsigned char *given,\n+\t\t\t     unsigned char *commit)\n+{\n+\tif (table->nr && find_merge_parent(table, given, commit))\n+\t\treturn;\n+\tALLOC_GROW(table->item, table->nr + 1, table->alloc);\n+\thashcpy(table->item[table->nr].given, given);\n+\thashcpy(table->item[table->nr].commit, commit);\n+\ttable->nr++;\n+}\n+\n+static int handle_line(char *line, struct merge_parents *merge_parents)\n {\n \tint i, len = strlen(line);\n \tstruct origin_data *origin_data;\n@@ -61,6 +101,7 @@ static int handle_line(char *line)\n \tstruct src_data *src_data;\n \tstruct string_list_item *item;\n \tint pulling_head = 0;\n+\tunsigned char sha1[20];\n \n \tif (len < 43 || line[40] != '\\t')\n \t\treturn 1;\n@@ -71,14 +112,15 @@ static int handle_line(char *line)\n \tif (line[41] != '\\t')\n \t\treturn 2;\n \n-\tline[40] = 0;\n-\torigin_data = xcalloc(1, sizeof(struct origin_data));\n-\ti = get_sha1(line, origin_data->sha1);\n-\tline[40] = '\\t';\n-\tif (i) {\n-\t\tfree(origin_data);\n+\ti = get_sha1_hex(line, sha1);\n+\tif (i)\n \t\treturn 3;\n-\t}\n+\n+\tif (!find_merge_parent(merge_parents, sha1, NULL))\n+\t\treturn 0; /* subsumed by other parents */\n+\n+\torigin_data = xcalloc(1, sizeof(struct origin_data));\n+\thashcpy(origin_data->sha1, sha1);\n \n \tif (line[len - 1] == '\\n')\n \t\tline[len - 1] = 0;\n@@ -366,6 +408,68 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \tstrbuf_release(&tagbuf);\n }\n \n+static struct merge_parents *find_merge_parents(struct strbuf *in, unsigned char *head)\n+{\n+\tstruct commit_list *parents, *next;\n+\tstruct commit *head_commit;\n+\tstruct merge_parents *result = xcalloc(1, sizeof(*result));\n+\tint pos = 0, i, j;\n+\n+\tparents = NULL;\n+\twhile (pos < in->len) {\n+\t\tint len;\n+\t\tchar *p = in->buf + pos;\n+\t\tchar *newline = strchr(p, '\\n');\n+\t\tunsigned char sha1[20];\n+\t\tstruct commit *parent;\n+\t\tstruct object *obj;\n+\n+\t\tlen = newline ? newline - p : strlen(p);\n+\t\tpos += len + !!newline;\n+\n+\t\tif (len < 43 ||\n+\t\t    get_sha1_hex(p, sha1) ||\n+\t\t    p[40] != '\\t' ||\n+\t\t    p[41] != '\\t')\n+\t\t\tcontinue; /* skip not-for-merge */\n+\t\t/*\n+\t\t * Do not use get_merge_parent() here; we do not have\n+\t\t * \"name\" here and we do not want to contaminate its\n+\t\t * util field yet.\n+\t\t */\n+\t\tobj = parse_object(sha1);\n+\t\tparent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);\n+\t\tif (!parent)\n+\t\t\tcontinue;\n+\t\tcommit_list_insert(parent, &parents);\n+\t\tadd_merge_parent(result, obj->sha1, parent->object.sha1);\n+\t}\n+\thead_commit = lookup_commit(head);\n+\tif (head_commit)\n+\t\tcommit_list_insert(head_commit, &parents);\n+\tparents = reduce_heads(parents);\n+\n+\twhile (parents) {\n+\t\tfor (i = 0; i < result->nr; i++)\n+\t\t\tif (!hashcmp(result->item[i].commit,\n+\t\t\t\t     parents->item->object.sha1))\n+\t\t\t\tresult->item[i].used = 1;\n+\t\tnext = parents->next;\n+\t\tfree(parents);\n+\t\tparents = next;\n+\t}\n+\n+\tfor (i = j = 0; i < result->nr; i++) {\n+\t\tif (result->item[i].used) {\n+\t\t\tif (i != j)\n+\t\t\t\tresult->item[j] = result->item[i];\n+\t\t\tj++;\n+\t\t}\n+\t}\n+\tresult->nr = j;\n+\treturn result;\n+}\n+\n int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t  struct fmt_merge_msg_opts *opts)\n {\n@@ -373,6 +477,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \tunsigned char head_sha1[20];\n \tconst char *current_branch;\n \tvoid *current_branch_to_free;\n+\tstruct merge_parents *merge_parents;\n \n \t/* get current branch */\n \tcurrent_branch = current_branch_to_free =\n@@ -382,6 +487,8 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \tif (!prefixcmp(current_branch, \"refs/heads/\"))\n \t\tcurrent_branch += 11;\n \n+\tmerge_parents = find_merge_parents(in, head_sha1);\n+\n \t/* get a line */\n \twhile (pos < in->len) {\n \t\tint len;\n@@ -392,7 +499,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\tpos += len + !!newline;\n \t\ti++;\n \t\tp[len] = 0;\n-\t\tif (handle_line(p))\n+\t\tif (handle_line(p, merge_parents))\n \t\t\tdie (\"Error in line %d: %.*s\", i, len, p);\n \t}\n \ndiff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh\nindex a3b08a6..9894895 100755\n--- a/t/t7603-merge-reduce-heads.sh\n+++ b/t/t7603-merge-reduce-heads.sh\n@@ -57,7 +57,36 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '\n \ttest -f c2.c &&\n \ttest -f c3.c &&\n \ttest -f c4.c &&\n-\ttest -f c5.c\n+\ttest -f c5.c &&\n+\tgit show --format=%s -s >actual &&\n+\t! grep c1 actual &&\n+\tgrep c2 actual &&\n+\tgrep c3 actual &&\n+\t! grep c4 actual &&\n+\tgrep c5 actual\n+'\n+\n+test_expect_success 'pull c2, c3, c4, c5 into c1' '\n+\tgit reset --hard c1 &&\n+\tgit pull . c2 c3 c4 c5 &&\n+\ttest \"$(git rev-parse c1)\" != \"$(git rev-parse HEAD)\" &&\n+\ttest \"$(git rev-parse c1)\" = \"$(git rev-parse HEAD^1)\" &&\n+\ttest \"$(git rev-parse c2)\" = \"$(git rev-parse HEAD^2)\" &&\n+\ttest \"$(git rev-parse c3)\" = \"$(git rev-parse HEAD^3)\" &&\n+\ttest \"$(git rev-parse c5)\" = \"$(git rev-parse HEAD^4)\" &&\n+\tgit diff --exit-code &&\n+\ttest -f c0.c &&\n+\ttest -f c1.c &&\n+\ttest -f c2.c &&\n+\ttest -f c3.c &&\n+\ttest -f c4.c &&\n+\ttest -f c5.c &&\n+\tgit show --format=%s -s >actual &&\n+\t! grep c1 actual &&\n+\tgrep c2 actual &&\n+\tgrep c3 actual &&\n+\t! grep c4 actual &&\n+\tgrep c5 actual\n '\n \n test_expect_success 'setup' '\n-- \n1.7.10.333.gf16fa\n"}]}