{"thread":{"id":"66094","subject":"[BUG] \"commit graph is likely corrupt\" on git rebase","startedAt":"2026-07-31T09:36:40Z","lastAt":"2026-09-23T12:50:04Z","messageCount":6,"participants":["Florian Schmidt","Patrick Steinhardt","Orestis Floros","Kristofer Karlsson","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"549340","messageId":"d3241733-d015-4646-88e0-06e56a04e77b@nutanix.com","threadId":"66094","inReplyTo":null,"subject":"[BUG] \"commit graph is likely corrupt\" on git rebase","fromName":"Florian Schmidt","fromEmail":"flosch@nutanix.com","sentAt":"2026-07-31T09:05:20Z","receivedAt":"2026-07-31T09:36:40Z","isPatch":false,"body":"Hiya,\n\nHitting an issue during git rebase. I am starting to suspect git is \npossibly getting confused between main and submodule repos. I noticed \nthis with a self-compiled git 2.54, but I compiled git v2.55 just now, \nand seem to hit the same issue still. Full bisect at the end of the message.\n\nThe first-order issue, trying to rebase a work/foo branch which is \ntracking origin/master.  There's only one commit difference, a commit \nthat changes two submodule pointers:\n\n> devvm:~/gitrepo[git:work/foo *%|u+1-116]$ git show --format=\"\" -p\n> diff --git a/rpmsrc/kernel b/rpmsrc/kernel\n> index d8d411244..17006b605 160000\n> --- a/rpmsrc/kernel\n> +++ b/rpmsrc/kernel\n> @@ -1 +1 @@\n> -Subproject commit d8d411244cfb3463ce09b9577970f7955073ed14\n> +Subproject commit 17006b605eaed1820ae1c717d4782728b9afc6d3\n> diff --git a/rpmsrc/qemu-kvm b/rpmsrc/qemu-kvm\n> index e7d525d0f..8bf4019df 160000\n> --- a/rpmsrc/qemu-kvm\n> +++ b/rpmsrc/qemu-kvm\n> @@ -1 +1 @@\n> -Subproject commit e7d525d0fdc42fca65c2b70bb5f56adc5aa1d7b1\n> +Subproject commit 8bf4019dfcb320108a0f014d92d926320a4c8058\n> \n> devvm:~/gitrepo[git:work/foo *%|u+1-116]$ git rebase\n> error: Could not read 157b7492624beac3ac667dd3a84de9969377866b\n> fatal: invalid commit position. commit-graph is likely corrupt\n> \n> devvm:~/gitrepo[git:work/foo *%|u+1-116]$ git cat-file -t 157b7492624beac3ac667dd3a84de9969377866b\n> fatal: git cat-file: could not get object info\n\ngit fsck reported no corruption, just a bunch of dangling \ncommits/trees/blobs, no warnings about \n157b7492624beac3ac667dd3a84de9969377866b\n\nNow, hint 1: the rebase *does* work when disabling commit-graphs:\n\n> devvm:~/gitrepo[git:work/foo *%|u+1-116]$ git -c core.commitGraph=false rebase origin/master\n> Rebasing (1/1)\n> [... thinking thinking thinking for a long time ...]\n> Failed to merge submodule rpmsrc/kernel\n> CONFLICT (submodule): Merge conflict in rpmsrc/kernel\n> Failed to merge submodule rpmsrc/qemu-kvm\n> CONFLICT (submodule): Merge conflict in rpmsrc/qemu-kvm\n> [...]\n\nwhich is fair enough, the submodule pointers moved.\nAnd hint 2:\n\n> $ git cat-file -t 157b7492624beac3ac667dd3a84de9969377866b\n> fatal: git cat-file: could not get object info\n> $ git -C rpmsrc/kernel cat-file -t 157b7492624beac3ac667dd3a84de9969377866b\n> fatal: git cat-file: could not get object info\n> $ git -C rpmsrc/qemu-kvm cat-file -t 157b7492624beac3ac667dd3a84de9969377866b\n> commit\n\nSo it tries to identify something on the submodule? Maybe it gets \nconfused between submodule and main repo(?)\n\nFor good measure, I did a git fsck --full on rpmsrc/qemu-kvm, but again \nonly a few dangling warnings, and no change to the issue.\nSo I went to bisect and noticed the issue appeared somewhere between \nv2.53 and v2.54. v2.53 also complains, but then continues:\n\n> $ ~/upstream/git/git rebase\n> error: Could not read 157b7492624beac3ac667dd3a84de9969377866b\n> error: Could not read 95d676ad199191d81a000761733ea25c17172d8b\n> Failed to merge submodule rpmsrc/kernel (commits don't follow merge-base)\n> CONFLICT (submodule): Merge conflict in rpmsrc/kernel\n> Failed to merge submodule rpmsrc/qemu-kvm (commits don't follow merge-base)\n> CONFLICT (submodule): Merge conflict in rpmsrc/qemu-kvm\n> [...]\n\nIn any case, to figure out the cause, I bisected it down to commit \nbb5da75d61 (\"commit: use commit graph in \n`lookup_commit_reference_gently()`\"), which makes sense in the context \nof \"it works with commit-graph disabled\".\n\nAt this point, I wonder what the best next step is. I notice that there \nare two errors listed even when the \"git rebase\" works on v2.53.0, which \nare not there in the commitGraph=false case. I wonder whether that's a \nsign that something deeper is not as it should be. I'm happy to provide \nmore details as needed, but I didn't want to touch the repo at this \npoint and possibly lose the repro.\n\nCheers,\nFlorian\n"},{"id":"550187","messageId":"annVbVIKtPZJ7xG7@pks.im","threadId":"66094","inReplyTo":"d3241733-d015-4646-88e0-06e56a04e77b@nutanix.com","subject":"Re: [BUG] \"commit graph is likely corrupt\" on git rebase","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-10T13:43:09Z","receivedAt":"2026-08-10T13:43:16Z","isPatch":false,"body":"Hi Florian.\n\nOn Fri, Jul 31, 2026 at 10:05:20AM +0100, Florian Schmidt wrote:\n> Hiya,\n> \n> Hitting an issue during git rebase. I am starting to suspect git is possibly\n> getting confused between main and submodule repos. I noticed this with a\n> self-compiled git 2.54, but I compiled git v2.55 just now, and seem to hit\n> the same issue still. Full bisect at the end of the message.\n\nDid you by any chance also test the \"master\" branch? There's been some\nfixes to the commit graph that weren't released as part of Git 2.55 yet,\nso chance are those could fix the issue for you.\n\n> The first-order issue, trying to rebase a work/foo branch which is tracking\n> origin/master.  There's only one commit difference, a commit that changes\n> two submodule pointers:\n\nDo you by any chance have a minimal reproducer? That'd help us\ntremendously to reproduce and then fix the issue if it wasn't already\nfixed on \"master\".\n\nThanks!\n\nPatrick\n"},{"id":"552786","messageId":"20260916134632.1424829-1-orestisflo@gmail.com","threadId":"66094","inReplyTo":"d3241733-d015-4646-88e0-06e56a04e77b@nutanix.com","subject":"[PATCH] commit-reach: parse commits in the given repository","fromName":"Orestis Floros","fromEmail":"orestisflo@gmail.com","sentAt":"2026-09-16T13:46:31Z","receivedAt":"2026-09-16T13:46:45Z","isPatch":true,"body":"`can_all_from_reach()` and `can_all_from_reach_with_flag()` parse the\ncommits they walk in `the_repository`, even though their caller may be\nworking in a different repository. `repo_is_descendant_of()` is such a\ncaller: it is told which repository to work in, but as soon as\ngeneration numbers are enabled it hands the commits over to\n`can_all_from_reach()`, which then parses them elsewhere.\n\nThis breaks merging a superproject whose submodule pointer advanced on\nboth sides. merge-ort resolves it by calling `repo_in_merge_bases()` on\nthe submodule, and with a commit-graph in both the superproject and the\nsubmodule the merge dies:\n\n    $ git merge side\n    fatal: invalid commit position. commit-graph is likely corrupt\n\n`merge_submodule()` looks the submodule commits up in the submodule, so\nwalking their ancestry pulls in parents whose commit-graph position was\nrecorded while reading the submodule's commit-graph. The walk then\nparses those parents in `the_repository`, where the recorded position\nindexes the superproject's commit-graph instead: `fill_commit_graph_info()`\ndies when the position is out of bounds, and quietly returns another\ncommit's date, generation and parents when it is not.\n\nThe latter used to be the only symptom. Before bb5da75d61 (commit: use\ncommit graph in `lookup_commit_reference_gently()`, 2026-02-16) the\ninitial lookup did not record commit-graph positions, so the walk simply\nfailed to find the submodule commits in the superproject:\n\n    error: Could not read <commit>\n    Failed to merge submodule sub (commits don't follow merge-base)\n\nPass the repository into both functions. git-fetch-pack(1) and\ngit-upload-pack(1) keep passing `the_repository`.\n\nReported-by: Florian Schmidt <flosch@nutanix.com>\nSigned-off-by: Orestis Floros <orestisflo@gmail.com>\n---\n commit-reach.c             | 22 ++++++++++-----------\n commit-reach.h             |  7 ++++---\n fetch-pack.c               |  4 ++--\n t/helper/test-reach.c      |  5 +++--\n t/t6437-submodule-merge.sh | 39 ++++++++++++++++++++++++++++++++++++++\n upload-pack.c              |  6 +++---\n 6 files changed, 62 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5df471a313..3d579d8f7f 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -572,7 +572,7 @@ int repo_is_descendant_of(struct repository *r,\n \t\tstruct commit_list *from_list = NULL;\n \t\tint result;\n \t\tcommit_list_insert(commit, &from_list);\n-\t\tresult = can_all_from_reach(from_list, with_commit, 0);\n+\t\tresult = can_all_from_reach(r, from_list, with_commit, 0);\n \t\tcommit_list_free(from_list);\n \t\treturn result;\n \t} else {\n@@ -853,7 +853,8 @@ int commit_contains(struct ref_filter *filter, struct commit *commit,\n \treturn repo_is_descendant_of(the_repository, commit, list);\n }\n \n-int can_all_from_reach_with_flag(struct object_array *from,\n+int can_all_from_reach_with_flag(struct repository *r,\n+\t\t\t\t struct object_array *from,\n \t\t\t\t unsigned int with_flag,\n \t\t\t\t unsigned int assign_flag,\n \t\t\t\t timestamp_t min_commit_date,\n@@ -872,8 +873,7 @@ int can_all_from_reach_with_flag(struct object_array *from,\n \t\tif (!from_one || from_one->flags & assign_flag)\n \t\t\tcontinue;\n \n-\t\tfrom_one = deref_tag(the_repository, from_one,\n-\t\t\t\t     \"a from object\", 0);\n+\t\tfrom_one = deref_tag(r, from_one, \"a from object\", 0);\n \t\tif (!from_one || from_one->type != OBJ_COMMIT) {\n \t\t\t/*\n \t\t\t * no way to tell if this is reachable by\n@@ -886,7 +886,7 @@ int can_all_from_reach_with_flag(struct object_array *from,\n \t\t}\n \n \t\tlist[nr_commits] = (struct commit *)from_one;\n-\t\tif (repo_parse_commit(the_repository, list[nr_commits]) ||\n+\t\tif (repo_parse_commit(r, list[nr_commits]) ||\n \t\t    commit_graph_generation(list[nr_commits]) < min_generation) {\n \t\t\tresult = 0;\n \t\t\tgoto cleanup;\n@@ -921,7 +921,7 @@ int can_all_from_reach_with_flag(struct object_array *from,\n \t\t\t\tif (!(parent->item->object.flags & assign_flag)) {\n \t\t\t\t\tparent->item->object.flags |= assign_flag;\n \n-\t\t\t\t\tif (repo_parse_commit(the_repository, parent->item) ||\n+\t\t\t\t\tif (repo_parse_commit(r, parent->item) ||\n \t\t\t\t\t    parent->item->date < min_commit_date ||\n \t\t\t\t\t    commit_graph_generation(parent->item) < min_generation)\n \t\t\t\t\t\tcontinue;\n@@ -955,8 +955,8 @@ int can_all_from_reach_with_flag(struct object_array *from,\n \treturn result;\n }\n \n-int can_all_from_reach(struct commit_list *from, struct commit_list *to,\n-\t\t       int cutoff_by_min_date)\n+int can_all_from_reach(struct repository *r, struct commit_list *from,\n+\t\t       struct commit_list *to, int cutoff_by_min_date)\n {\n \tstruct object_array from_objs = OBJECT_ARRAY_INIT;\n \tstruct commit_list *from_iter = from, *to_iter = to;\n@@ -967,7 +967,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,\n \twhile (from_iter) {\n \t\tadd_object_array(&from_iter->item->object, NULL, &from_objs);\n \n-\t\tif (!repo_parse_commit(the_repository, from_iter->item)) {\n+\t\tif (!repo_parse_commit(r, from_iter->item)) {\n \t\t\ttimestamp_t generation;\n \t\t\tif (from_iter->item->date < min_commit_date)\n \t\t\t\tmin_commit_date = from_iter->item->date;\n@@ -981,7 +981,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,\n \t}\n \n \twhile (to_iter) {\n-\t\tif (!repo_parse_commit(the_repository, to_iter->item)) {\n+\t\tif (!repo_parse_commit(r, to_iter->item)) {\n \t\t\ttimestamp_t generation;\n \t\t\tif (to_iter->item->date < min_commit_date)\n \t\t\t\tmin_commit_date = to_iter->item->date;\n@@ -996,7 +996,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,\n \t\tto_iter = to_iter->next;\n \t}\n \n-\tresult = can_all_from_reach_with_flag(&from_objs, PARENT2, PARENT1,\n+\tresult = can_all_from_reach_with_flag(r, &from_objs, PARENT2, PARENT1,\n \t\t\t\t\t      min_commit_date, min_generation);\n \n \twhile (from) {\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 3f3a563d8a..5e2b82655b 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -88,13 +88,14 @@ int commit_contains(struct ref_filter *filter, struct commit *commit,\n  * commits with date below 'min_commit_date' or generation below\n  * 'min_generation'.\n  */\n-int can_all_from_reach_with_flag(struct object_array *from,\n+int can_all_from_reach_with_flag(struct repository *r,\n+\t\t\t\t struct object_array *from,\n \t\t\t\t unsigned int with_flag,\n \t\t\t\t unsigned int assign_flag,\n \t\t\t\t timestamp_t min_commit_date,\n \t\t\t\t timestamp_t min_generation);\n-int can_all_from_reach(struct commit_list *from, struct commit_list *to,\n-\t\t       int commit_date_cutoff);\n+int can_all_from_reach(struct repository *r, struct commit_list *from,\n+\t\t       struct commit_list *to, int commit_date_cutoff);\n \n \n /*\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..26dbbdc4f6 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -2330,8 +2330,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\tdie(_(\"unexpected 'ready' from remote\"));\n \t\telse\n \t\t\tdo_check_stateless_delimiter(stateless_rpc, &reader);\n-\t\tif (can_all_from_reach_with_flag(&nt_object_array, COMMON,\n-\t\t\t\t\t\t REACH_SCRATCH, 0,\n+\t\tif (can_all_from_reach_with_flag(the_repository, &nt_object_array,\n+\t\t\t\t\t\t COMMON, REACH_SCRATCH, 0,\n \t\t\t\t\t\t min_generation))\n \t\t\tlast_iteration = 1;\n \t\ttrace2_region_leave_printf(\"negotiation\", \"round\",\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 5d86a96c17..8ea77a782c 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -127,7 +127,7 @@ int cmd__reach(int ac, const char **av)\n \t\tprint_sorted_commit_ids(list);\n \t\tcommit_list_free(list);\n \t} else if (!strcmp(av[1], \"can_all_from_reach\")) {\n-\t\tprintf(\"%s(X,Y):%d\\n\", av[1], can_all_from_reach(X, Y, 1));\n+\t\tprintf(\"%s(X,Y):%d\\n\", av[1], can_all_from_reach(r, X, Y, 1));\n \t} else if (!strcmp(av[1], \"can_all_from_reach_with_flag\")) {\n \t\tstruct commit_list *iter = Y;\n \n@@ -136,7 +136,8 @@ int cmd__reach(int ac, const char **av)\n \t\t\titer = iter->next;\n \t\t}\n \n-\t\tprintf(\"%s(X,_,_,0,0):%d\\n\", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0));\n+\t\tprintf(\"%s(X,_,_,0,0):%d\\n\", av[1],\n+\t\t       can_all_from_reach_with_flag(r, &X_obj, 2, 4, 0, 0));\n \t} else if (!strcmp(av[1], \"commit_contains\")) {\n \t\tstruct ref_filter filter = REF_FILTER_INIT;\n \t\tstruct contains_cache cache;\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex a564758f52..afb484b963 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -517,4 +517,43 @@ test_expect_success 'merging should fail with no merge base' '\n \t)\n '\n \n+test_expect_success 'setup for commit-graphs in superproject and submodule' '\n+\tgit init commit-graph &&\n+\t(cd commit-graph &&\n+\tgit init sub &&\n+\tfor i in 1 2 3 4 5 6 7 8 9 10\n+\tdo\n+\t\ttest_commit -C sub \"sub-$i\" || return 1\n+\tdone &&\n+\n+\tgit -C sub checkout --detach sub-1 &&\n+\tgit add sub &&\n+\tgit commit -m base &&\n+\n+\t# Write the commit-graph of the superproject while it only\n+\t# contains a single commit. Any commit-graph position of the\n+\t# submodule is thus out of bounds for the superproject.\n+\tgit commit-graph write --reachable &&\n+\n+\tgit checkout -b side &&\n+\tgit -C sub checkout --detach sub-5 &&\n+\tgit add sub &&\n+\tgit commit -m side &&\n+\n+\tgit checkout main &&\n+\tgit -C sub checkout --detach sub-10 &&\n+\tgit add sub &&\n+\tgit commit -m main &&\n+\n+\tgit -C sub commit-graph write --reachable)\n+'\n+\n+test_expect_success 'merge does not mix up superproject and submodule commit-graphs' '\n+\t(cd commit-graph &&\n+\tgit merge side &&\n+\tgit rev-parse HEAD:sub >actual &&\n+\tgit -C sub rev-parse sub-10^{commit} >expect &&\n+\ttest_cmp expect actual)\n+'\n+\n test_done\ndiff --git a/upload-pack.c b/upload-pack.c\nindex a52856d869..550fea6763 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -565,9 +565,9 @@ static int ok_to_give_up(struct upload_pack_data *data)\n \tif (!data->have_obj.nr)\n \t\treturn 0;\n \n-\treturn can_all_from_reach_with_flag(&data->want_obj, THEY_HAVE,\n-\t\t\t\t\t    COMMON_KNOWN, data->oldest_have,\n-\t\t\t\t\t    min_generation);\n+\treturn can_all_from_reach_with_flag(the_repository, &data->want_obj,\n+\t\t\t\t\t    THEY_HAVE, COMMON_KNOWN,\n+\t\t\t\t\t    data->oldest_have, min_generation);\n }\n \n static int get_common_commits(struct upload_pack_data *data,\n\nbase-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc\n-- \n2.55.0\n\n"},{"id":"552789","messageId":"CAL71e4PRqawd=2DfLELLXR2g0PFz2ehXf05uLXJKF92WiztO9w@mail.gmail.com","threadId":"66094","inReplyTo":"20260916134632.1424829-1-orestisflo@gmail.com","subject":"Re: [PATCH] commit-reach: parse commits in the given repository","fromName":"Kristofer Karlsson","fromEmail":"krka@spotify.com","sentAt":"2026-09-16T14:55:33Z","receivedAt":"2026-09-16T14:55:46Z","isPatch":true,"body":"On Wed, 16 Sept 2026 at 16:00, Orestis Floros <orestisflo@gmail.com> wrote:\n> +test_expect_success 'setup for commit-graphs in superproject and submodule' '\n> +       git init commit-graph &&\n> +       (cd commit-graph &&\n> +       git init sub &&\n> +       for i in 1 2 3 4 5 6 7 8 9 10\n> +       do\n> +               test_commit -C sub \"sub-$i\" || return 1\n> +       done &&\n> +\n> +       git -C sub checkout --detach sub-1 &&\n> +       git add sub &&\n> +       git commit -m base &&\n> +\n> +       # Write the commit-graph of the superproject while it only\n> +       # contains a single commit. Any commit-graph position of the\n> +       # submodule is thus out of bounds for the superproject.\n> +       git commit-graph write --reachable &&\n> +\n> +       git checkout -b side &&\n> +       git -C sub checkout --detach sub-5 &&\n> +       git add sub &&\n> +       git commit -m side &&\n> +\n> +       git checkout main &&\n> +       git -C sub checkout --detach sub-10 &&\n> +       git add sub &&\n> +       git commit -m main &&\n> +\n> +       git -C sub commit-graph write --reachable)\n> +'\n> +\n> +test_expect_success 'merge does not mix up superproject and submodule commit-graphs' '\n> +       (cd commit-graph &&\n> +       git merge side &&\n> +       git rev-parse HEAD:sub >actual &&\n> +       git -C sub rev-parse sub-10^{commit} >expect &&\n> +       test_cmp expect actual)\n> +'\n> +\n>  test_done\n\nI ran the new tests with and without the code change and can\nconfirm it reproduces the bug.\n\nThe code makes sense to me.  As a bonus, this brings\ncommit-reach.c down from 17 to 11 the_repository references,\ngetting us slightly closer to eventually dropping\nUSE_THE_REPOSITORY_VARIABLE there.\n\n- Kristofer\n"},{"id":"552793","messageId":"xmqqld91w0ia.fsf@gitster.g","threadId":"66094","inReplyTo":"20260916134632.1424829-1-orestisflo@gmail.com","subject":"Re: [PATCH] commit-reach: parse commits in the given repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-16T17:48:29Z","receivedAt":"2026-09-16T17:48:34Z","isPatch":true,"body":"Orestis Floros <orestisflo@gmail.com> writes:\n\n> ...\n> The latter used to be the only symptom. Before bb5da75d61 (commit: use\n> commit graph in `lookup_commit_reference_gently()`, 2026-02-16) the\n> initial lookup did not record commit-graph positions, so the walk simply\n> failed to find the submodule commits in the superproject:\n>\n>     error: Could not read <commit>\n>     Failed to merge submodule sub (commits don't follow merge-base)\n>\n> Pass the repository into both functions. git-fetch-pack(1) and\n> git-upload-pack(1) keep passing `the_repository`.\n\nThat's reasonable.  It is a very welcome change that aligns with\nrecent libification efforts.\n\nWill queue.\n\nThanks.\n"},{"id":"553047","messageId":"arPK8phxWv1pNG_m@pks.im","threadId":"66094","inReplyTo":"20260916134632.1424829-1-orestisflo@gmail.com","subject":"Re: [PATCH] commit-reach: parse commits in the given repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-23T12:49:54Z","receivedAt":"2026-09-23T12:50:04Z","isPatch":true,"body":"On Wed, Sep 16, 2026 at 03:46:31PM +0200, Orestis Floros wrote:\n> `can_all_from_reach()` and `can_all_from_reach_with_flag()` parse the\n> commits they walk in `the_repository`, even though their caller may be\n> working in a different repository. `repo_is_descendant_of()` is such a\n> caller: it is told which repository to work in, but as soon as\n> generation numbers are enabled it hands the commits over to\n> `can_all_from_reach()`, which then parses them elsewhere.\n> \n> This breaks merging a superproject whose submodule pointer advanced on\n> both sides. merge-ort resolves it by calling `repo_in_merge_bases()` on\n> the submodule, and with a commit-graph in both the superproject and the\n> submodule the merge dies:\n> \n>     $ git merge side\n>     fatal: invalid commit position. commit-graph is likely corrupt\n> \n> `merge_submodule()` looks the submodule commits up in the submodule, so\n> walking their ancestry pulls in parents whose commit-graph position was\n> recorded while reading the submodule's commit-graph. The walk then\n> parses those parents in `the_repository`, where the recorded position\n> indexes the superproject's commit-graph instead: `fill_commit_graph_info()`\n> dies when the position is out of bounds, and quietly returns another\n> commit's date, generation and parents when it is not.\n> \n> The latter used to be the only symptom. Before bb5da75d61 (commit: use\n> commit graph in `lookup_commit_reference_gently()`, 2026-02-16) the\n> initial lookup did not record commit-graph positions, so the walk simply\n> failed to find the submodule commits in the superproject:\n> \n>     error: Could not read <commit>\n>     Failed to merge submodule sub (commits don't follow merge-base)\n> \n> Pass the repository into both functions. git-fetch-pack(1) and\n> git-upload-pack(1) keep passing `the_repository`.\n\nThanks for the nice explanation.\n\n> diff --git a/commit-reach.c b/commit-reach.c\n> index 5df471a313..3d579d8f7f 100644\n> --- a/commit-reach.c\n> +++ b/commit-reach.c\n\nI'm always a fan of removing this implicit dependency. Doubly so if it\nactually fixes a bug.\n\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index a564758f52..afb484b963 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -517,4 +517,43 @@ test_expect_success 'merging should fail with no merge base' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'setup for commit-graphs in superproject and submodule' '\n> +\tgit init commit-graph &&\n> +\t(cd commit-graph &&\n\nI wanted to complain about formatting at first, but I see that you\nsimply follow the preexisting style in this file. So I guess this is\nokay.\n\nI also double-checked that the test indeed catches the bug.\n\nSo overall, this looks good to me. Thanks!\n\nPatrick\n"}]}