{"thread":{"id":"65908","subject":"Bug report - git rev-list --exclude-first-parent-only [SEC=UNOFFICIAL]","startedAt":"2026-07-02T04:00:17Z","lastAt":"2026-07-21T00:35:39Z","messageCount":3,"participants":["Michael Hore","Junio C Hamano","Jerry Zhang"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"546953","messageId":"ME2PR01MB5490E3CE825C83474D1573CED1F52@ME2PR01MB5490.ausprd01.prod.outlook.com","threadId":"65908","inReplyTo":null,"subject":"Bug report - git rev-list --exclude-first-parent-only [SEC=UNOFFICIAL]","fromName":"Michael Hore","fromEmail":"michael.hore@asic.gov.au","sentAt":"2026-07-02T03:59:04Z","receivedAt":"2026-07-02T04:00:17Z","isPatch":false,"body":"I believe I have found a bug -\n\nMy repo has a commit structure like\n\nR2\n|\\\n| F\n|/\nR1\n\ni.e.\n - there is a merge commit R2 with parents R1 and F\n - the parent of F is R1\n\nI ran \"git rev-list --exclude-first-parent-only F ^R2\"\n\nit gave the expected result: \"F\"\n\nI ran \"git rev-list --exclude-first-parent-only F R1 ^R2\"\n\nI expected the same result, but I got an unexpected result - nothing at all\n\nSuspected cause - I had a look at the code, and it looks like process_parents() in revision.c, when processing uninteresting flags, will skip the 1st parent and mark the 2nd parent as uninteresting if the 1st parent is already SEEN, even with the flag exclude-first-parent-only. I think maybe explicitly selecting R1 on the command line causes it to be marked SEEN before ^R2 is processed, thus resulting in F being marked uninteresting.\n\n[System Info]\ngit version:\ngit version 2.54.0.windows.1\ncpu: x86_64\nbuilt from commit: 2b8a3ab140826ac423c2845ef81d4c6ac4f7bf3c\nsizeof-long: 4\nsizeof-size_t: 8\nshell-path: D:/git-sdk-64-build-installers/usr/bin/sh\nrust: disabled\nfeature: fsmonitor--daemon\ngettext: enabled\nSHA-1: SHA1_DC\nSHA-256: SHA256_BLK\ndefault-ref-format: files\ndefault-hash: sha1\n\n\n[Enabled Hooks]\n\nRegards,\nMichael\n\nPlease consider the environment before printing this document.\n\nInformation collected by ASIC may contain personal information. Please refer to our Privacy Policy<https://asic.gov.au/privacy/> for information about how we handle your personal information, your rights to seek access to and correct your personal information, and how to complain about breaches of your privacy by ASIC.\n\nThis e-mail and any attachments are intended for the addressee(s) only and may be confidential. They may contain legally privileged, copyright material or personal and /or confidential information. You should not read, copy, use or disclose the content without authorisation. If you have received this email in error, please notify the sender as soon as possible, delete the email and destroy any copies. This notice should not be removed.\n"},{"id":"547116","messageId":"xmqqbjcnizr1.fsf@gitster.g","threadId":"65908","inReplyTo":"ME2PR01MB5490E3CE825C83474D1573CED1F52@ME2PR01MB5490.ausprd01.prod.outlook.com","subject":"Re: Bug report - git rev-list --exclude-first-parent-only [SEC=UNOFFICIAL]","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-03T20:28:50Z","receivedAt":"2026-07-03T20:28:53Z","isPatch":false,"body":"Michael Hore <Michael.Hore@asic.gov.au> writes:\n\n> I believe I have found a bug -\n>\n> My repo has a commit structure like\n>\n> R2\n> |\\\n> | F\n> |/\n> R1\n>\n> i.e.\n>  - there is a merge commit R2 with parents R1 and F\n>  - the parent of F is R1\n\nIOW, R2 is a useless merge that could have been a simple\nfast-forward directly to F.\n\n> I ran \"git rev-list --exclude-first-parent-only F ^R2\"\n>\n> it gave the expected result: \"F\"\n>\n> I ran \"git rev-list --exclude-first-parent-only F R1 ^R2\"\n>\n> I expected the same result, but I got an unexpected result - nothing at all\n\nThis seems to have come from 9d505b7b49 (git-rev-list: add\n--exclude-first-parent-only flag, 2022-01-11).  I do not know if the\noriginal author is still around, but it would have been nicer to ask\nfor input from them (cc'ed).\n\nA fix could be something along this line, but I've never used this\nfeature even once (I instead use Michael Haggerty's exellent \"git\nwhen-merged\" thing), so I may very well be breaking _other_ use\ncases this feature was originally intended for without knowing.\n\nThe patched part is inside a huge \"while (parent)\" loop.  The idea\nis to break out before the loop goes on to smudge later parents when\nwe are in the \"smudge only first parent as uninteresting, without\ncontaminating the history leading to other parents\" mode.\n\n revision.c                   | 10 ++++++++--\n t/t6012-rev-list-simplify.sh | 18 ++++++++++++++++++\n 2 files changed, 26 insertions(+), 2 deletions(-)\n\ndiff --git c/revision.c w/revision.c\nindex e91d7e1f11..1f50d42a7a 100644\n--- c/revision.c\n+++ w/revision.c\n@@ -1151,12 +1151,18 @@ static int process_parents(struct rev_info *revs, struct commit *commit,\n \t\t\tif (p)\n \t\t\t\tp->object.flags |= UNINTERESTING |\n \t\t\t\t\t\t   CHILD_VISITED;\n-\t\t\tif (repo_parse_commit_gently(revs->repo, p, 1) < 0)\n+\t\t\tif (repo_parse_commit_gently(revs->repo, p, 1) < 0) {\n+\t\t\t\tif (revs->exclude_first_parent_only)\n+\t\t\t\t\tbreak;\n \t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (p->parents)\n \t\t\t\tmark_parents_uninteresting(revs, p);\n-\t\t\tif (p->object.flags & SEEN)\n+\t\t\tif (p->object.flags & SEEN) {\n+\t\t\t\tif (revs->exclude_first_parent_only)\n+\t\t\t\t\tbreak;\n \t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tp->object.flags |= (SEEN | NOT_USER_GIVEN);\n \t\t\tif (queue)\n \t\t\t\tprio_queue_put(queue, p);\ndiff --git c/t/t6012-rev-list-simplify.sh w/t/t6012-rev-list-simplify.sh\nindex 4cecb6224c..2284bbba12 100755\n--- c/t/t6012-rev-list-simplify.sh\n+++ w/t/t6012-rev-list-simplify.sh\n@@ -285,4 +285,22 @@ test_expect_success 'log --graph --simplify-merges --show-pulls' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'exclude-first-parent-only with parent already seen' '\n+\tgit checkout --orphan test-seen &&\n+\tgit rm -rf . &&\n+\ttest_commit r1 &&\n+\tgit checkout -b branch-f &&\n+\ttest_commit f &&\n+\tgit checkout test-seen &&\n+\tgit merge --no-ff --no-edit -m r2 branch-f &&\n+\tgit tag r2 &&\n+\n+\tgit rev-list --exclude-first-parent-only f ^r2 >actual &&\n+\tgit rev-parse f >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit rev-list --exclude-first-parent-only f r1 ^r2 >actual2 &&\n+\ttest_cmp expect actual2\n+'\n+\n test_done\n\n"},{"id":"548708","messageId":"CAMKO5Cu0i3UKT61th3ZUiiQTkPa+YtxGQAQSXJjNWGWaTs8OGg@mail.gmail.com","threadId":"65908","inReplyTo":"xmqqbjcnizr1.fsf@gitster.g","subject":"Re: Bug report - git rev-list --exclude-first-parent-only [SEC=UNOFFICIAL]","fromName":"Jerry Zhang","fromEmail":"jerry@skydio.com","sentAt":"2026-07-21T00:35:27Z","receivedAt":"2026-07-21T00:35:39Z","isPatch":false,"body":"On Fri, Jul 3, 2026 at 1:28 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Michael Hore <Michael.Hore@asic.gov.au> writes:\n>\n> > I believe I have found a bug -\n> >\n> > My repo has a commit structure like\n> >\n> > R2\n> > |\\\n> > | F\n> > |/\n> > R1\n> >\n> > i.e.\n> >  - there is a merge commit R2 with parents R1 and F\n> >  - the parent of F is R1\n>\n> IOW, R2 is a useless merge that could have been a simple\n> fast-forward directly to F.\n>\n> > I ran \"git rev-list --exclude-first-parent-only F ^R2\"\n> >\n> > it gave the expected result: \"F\"\n> >\n> > I ran \"git rev-list --exclude-first-parent-only F R1 ^R2\"\n> >\n> > I expected the same result, but I got an unexpected result - nothing at all\n>\n> This seems to have come from 9d505b7b49 (git-rev-list: add\n> --exclude-first-parent-only flag, 2022-01-11).  I do not know if the\n> original author is still around, but it would have been nicer to ask\n> for input from them (cc'ed).\n>\n> A fix could be something along this line, but I've never used this\n> feature even once (I instead use Michael Haggerty's exellent \"git\n> when-merged\" thing), so I may very well be breaking _other_ use\n> cases this feature was originally intended for without knowing.\nfwiw when-merged seems to be asking the question \"when was X branch\nmerged into the\nbaseline\", while exclude-first-parent-only is asking \"when did X\nbranch first split off from\nthe baseline\". of course that property may not be interesting to you\nif you're looking for the\nformer.\n>\n> The patched part is inside a huge \"while (parent)\" loop.  The idea\n> is to break out before the loop goes on to smudge later parents when\n> we are in the \"smudge only first parent as uninteresting, without\n> contaminating the history leading to other parents\" mode.\n>\n>  revision.c                   | 10 ++++++++--\n>  t/t6012-rev-list-simplify.sh | 18 ++++++++++++++++++\n>  2 files changed, 26 insertions(+), 2 deletions(-)\n>\n> diff --git c/revision.c w/revision.c\n> index e91d7e1f11..1f50d42a7a 100644\n> --- c/revision.c\n> +++ w/revision.c\n> @@ -1151,12 +1151,18 @@ static int process_parents(struct rev_info *revs, struct commit *commit,\n>                         if (p)\n>                                 p->object.flags |= UNINTERESTING |\n>                                                    CHILD_VISITED;\n> -                       if (repo_parse_commit_gently(revs->repo, p, 1) < 0)\n> +                       if (repo_parse_commit_gently(revs->repo, p, 1) < 0) {\n> +                               if (revs->exclude_first_parent_only)\n> +                                       break;\n>                                 continue;\n> +                       }\n>                         if (p->parents)\n>                                 mark_parents_uninteresting(revs, p);\n> -                       if (p->object.flags & SEEN)\n> +                       if (p->object.flags & SEEN) {\n> +                               if (revs->exclude_first_parent_only)\n> +                                       break;\n>                                 continue;\n> +                       }\n>                         p->object.flags |= (SEEN | NOT_USER_GIVEN);\n>                         if (queue)\n>                                 prio_queue_put(queue, p);\n> diff --git c/t/t6012-rev-list-simplify.sh w/t/t6012-rev-list-simplify.sh\n> index 4cecb6224c..2284bbba12 100755\n> --- c/t/t6012-rev-list-simplify.sh\n> +++ w/t/t6012-rev-list-simplify.sh\n> @@ -285,4 +285,22 @@ test_expect_success 'log --graph --simplify-merges --show-pulls' '\n>         test_cmp expect actual\n>  '\n>\n> +test_expect_success 'exclude-first-parent-only with parent already seen' '\n> +       git checkout --orphan test-seen &&\n> +       git rm -rf . &&\n> +       test_commit r1 &&\n> +       git checkout -b branch-f &&\n> +       test_commit f &&\n> +       git checkout test-seen &&\n> +       git merge --no-ff --no-edit -m r2 branch-f &&\n> +       git tag r2 &&\n> +\n> +       git rev-list --exclude-first-parent-only f ^r2 >actual &&\n> +       git rev-parse f >expect &&\n> +       test_cmp expect actual &&\n> +\n> +       git rev-list --exclude-first-parent-only f r1 ^r2 >actual2 &&\n> +       test_cmp expect actual2\n> +'\n> +\n>  test_done\n>\nIts been a while since i've looked at the code, but the rationale and\ntest case make sense to me. thanks\n\nReviewed-by: Jerry Zhang <jerry@skydio.com>\n"}]}