{"thread":{"id":"66711","subject":"[PATCH 0/3] builtin/grep: fix reading objects from wrong repository","startedAt":"2026-10-30T10:21:46Z","lastAt":"2026-11-03T12:43:22Z","messageCount":10,"participants":["Patrick Steinhardt","Toon Claes"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"556877","messageId":"20261030-pks-grep-recurse-submodule-v1-0-d4eb5c87d8fb@pks.im","threadId":"66711","inReplyTo":null,"subject":"[PATCH 0/3] builtin/grep: fix reading objects from wrong repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-30T10:21:46Z","receivedAt":"2026-10-30T10:21:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis small patch series fixes the bug reported in [1], where doing `git\ngrep --recurse-submodules` with nested submodule trees fails to read the\ntrees. The only patch needed here is the first one, the other patches\nare only belt-and-suspenders as I've been checking whether we use the\nwrong repository anywhere else.\n\nAt the end of this patch series we only use `the_repository` in\n`cmd_grep()` itself. Ideally we'd adapt \"builtin/grep.c\" to get rid of\n`the_repository` altogether, but unfortunately it's a bit on the\ninvolved side as we also have to worry about the case where git-grep(1)\nis executed outside of a repository with \"--no-index\". So I skipped that\nrefactoring for now.\n\nThe patch is based on v2.56.0 so that it can be easily backported.\n\nThanks!\n\nPatrick\n\n[1]: <CAMtVYEjWd2XwJpXdk3PV2jbNdoOaqS3x+08EMu5byy55RxfRmA@mail.gmail.com>\n\n---\nPatrick Steinhardt (3):\n      builtin/grep: read submodule trees via their repository\n      builtin/grep: read sparse directory trees via passed-in repository\n      builtin/grep: read \".gitmodules\" via passed-in repository\n\n builtin/grep.c                     |  6 +++---\n t/t7814-grep-recurse-submodules.sh | 23 +++++++++++++++++++++++\n 2 files changed, 26 insertions(+), 3 deletions(-)\n\n\n---\nbase-commit: a018953688f1b10bddf91bff8747068f5f4746a4\nchange-id: 20261030-pks-grep-recurse-submodule-20177cc7794c\n\n\n"},{"id":"556878","messageId":"20261030-pks-grep-recurse-submodule-v1-1-d4eb5c87d8fb@pks.im","threadId":"66711","inReplyTo":"20261030-pks-grep-recurse-submodule-v1-0-d4eb5c87d8fb@pks.im","subject":"[PATCH 1/3] builtin/grep: read submodule trees via their repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-30T10:21:47Z","receivedAt":"2026-10-30T10:21:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `grep_tree()` searches for a specific string recursively in\na tree. It can recurse in two different ways:\n\n  - It may either see a subtree, in which case it recurses into itself.\n\n  - Or it may see a submodule, and in that case it recurses via\n    `grep_submodule()` which then again calls `grep_tree()`.\n\nIn the latter case we continue grepping in the submodule repository's\nobject graph. Historically this used to work by linking that submodule's\nobject directory into the main object database. But in 93cd344e34f\n(builtin/grep: stop registering submodule ODB as source, 2026-09-11) we\nstopped doing that and instead expect to read submodule objects via the\nsubmodule repository.\n\nThis logic is broken though: while we indeed know to read blobs via the\nsubmodule's object database, we didn't adapt trees in the same way. The\nresult is that Git fails to read the object:\n\n    $ git grep --recurse-submodules -e hello HEAD\n    fatal: unable to read tree (935cff11a11f8734cf4e0d40955d1e0e5c12038e)\n\nThis went unnoticed because our tests only cover submodules that contain\nfiles at the top level.\n\nFix this by reading nested trees via `opt->repo`, which is set to the\nsubmodule's repository via `grep_submodule()`.\n\nReported-by: Root <rootminus0x1@gmail.com>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c                     |  2 +-\n t/t7814-grep-recurse-submodules.sh | 23 +++++++++++++++++++++++\n 2 files changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex b045f8a488d..22fb365eeac 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -653,7 +653,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,\n \t\t\tvoid *data;\n \t\t\tsize_t size;\n \n-\t\t\tdata = odb_read_object(the_repository->objects,\n+\t\t\tdata = odb_read_object(repo->objects,\n \t\t\t\t\t       &entry.oid, &type, &size);\n \t\t\tif (!data)\n \t\t\t\tdie(_(\"unable to read tree (%s)\"),\ndiff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh\nindex 3d149d34c15..f51c5181805 100755\n--- a/t/t7814-grep-recurse-submodules.sh\n+++ b/t/t7814-grep-recurse-submodules.sh\n@@ -631,4 +631,27 @@ test_expect_success 'check scope of core.useReplaceRefs' '\n \tgit -C base grep --cached --recurse-submodules C\n '\n \n+test_expect_success 'grep --recurse-submodules with tree-ish and nested directory' '\n+\trm -rf sub super &&\n+\n+\tgit init sub &&\n+\tmkdir sub/dir &&\n+\techo hello >sub/top &&\n+\techo hello >sub/dir/nested &&\n+\tgit -C sub add top dir/nested &&\n+\tgit -C sub commit --message \"sub\" &&\n+\n+\ttest_config_global protocol.file.allow always &&\n+\tgit init super &&\n+\tgit -C super submodule add \"$(pwd)/sub\" sub &&\n+\tgit -C super commit --message \"super\" &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tHEAD:sub/dir/nested:hello\n+\tHEAD:sub/top:hello\n+\tEOF\n+\tgit -C super grep --recurse-submodules -e hello HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\n-- \n2.56.0.341.g393f52fd96.dirty\n\n\n"},{"id":"556879","messageId":"20261030-pks-grep-recurse-submodule-v1-2-d4eb5c87d8fb@pks.im","threadId":"66711","inReplyTo":"20261030-pks-grep-recurse-submodule-v1-0-d4eb5c87d8fb@pks.im","subject":"[PATCH 2/3] builtin/grep: read sparse directory trees via passed-in repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-30T10:21:48Z","receivedAt":"2026-10-30T10:21:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When iterating through the index in `grep_cache()` we may encounter\nsparse directory entries. In that case we read the tree object they\npoint to so that we can recurse into it via `grep_tree()`. We do so via\n`the_repository` though, even though `grep_cache()` may be called for a\nsubmodule via `grep_submodule()`.\n\nThis issue is similar to the one fixed in the preceding commit, where\n`grep_tree()` didn't use the correct repository. But in this case here\nthe issue cannot be triggered as we call `repo_read_gitmodules()` on the\nsubmodule repository, and that function will cause us to read its index.\nBut because we only ever set `command_requires_full_index` for the\ntop-level repository we will convert the submodule's index into a full\nindex, and consequently we will never see sparse directory entries. This\nis also documented as such in a NEEDSWORK comment in `grep_submodule()`.\n\nRegardless of that the code is still wrong, and it may very easily turn\ninto a bug once we start to handle sparse indices for submodules. Adapt\nit to use the correct repository to preempt such a future bug.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 22fb365eeac..cb518d8155e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -560,7 +560,7 @@ static int grep_cache(struct grep_opt *opt,\n \t\t\tvoid *data;\n \t\t\tsize_t size;\n \n-\t\t\tdata = odb_read_object(the_repository->objects, &ce->oid,\n+\t\t\tdata = odb_read_object(repo->objects, &ce->oid,\n \t\t\t\t\t       &type, &size);\n \t\t\tif (!data)\n \t\t\t\tdie(_(\"unable to read tree %s\"), oid_to_hex(&ce->oid));\n\n-- \n2.56.0.341.g393f52fd96.dirty\n\n\n"},{"id":"556880","messageId":"20261030-pks-grep-recurse-submodule-v1-3-d4eb5c87d8fb@pks.im","threadId":"66711","inReplyTo":"20261030-pks-grep-recurse-submodule-v1-0-d4eb5c87d8fb@pks.im","subject":"[PATCH 3/3] builtin/grep: read \".gitmodules\" via passed-in repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-30T10:21:49Z","receivedAt":"2026-10-30T10:21:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When grepping through a list of objects with \"--recurse-submodules\", we\ncall `gitmodules_config_oid()` to load gitmodules for the specific\nrevision. But instead of passing `opt->repo` we pass `the_repository` to\nthat function, which is somewhat fishy.\n\nThis is again not a bug because `grep_objects()` is only ever called by\n`cmd_grep()`, and consequently we know that `opt->repo` is always going\nto be set to `the_repository`. But it's a code smell regardless of that,\nso adapt the code to use `opt->repo` instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex cb518d8155e..22f0a1b0d0e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -883,7 +883,7 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,\n \t\tif (recurse_submodules) {\n \t\t\tsubmodule_free(opt->repo);\n \t\t\tobj_read_lock();\n-\t\t\tgitmodules_config_oid(the_repository, &real_obj->oid);\n+\t\t\tgitmodules_config_oid(opt->repo, &real_obj->oid);\n \t\t\tobj_read_unlock();\n \t\t}\n \t\tif (grep_object(opt, pathspec, real_obj, list->objects[i].name,\n\n-- \n2.56.0.341.g393f52fd96.dirty\n\n\n"},{"id":"556931","messageId":"87fqxnyx3s.fsf@dev.null.iotcl.com.invalid","threadId":"66711","inReplyTo":"20261030-pks-grep-recurse-submodule-v1-1-d4eb5c87d8fb@pks.im","subject":"Re: [PATCH 1/3] builtin/grep: read submodule trees via their repository","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-10-30T16:25:11Z","receivedAt":"2026-10-30T16:25:11Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `grep_tree()` searches for a specific string recursively in\n> a tree. It can recurse in two different ways:\n>\n>   - It may either see a subtree, in which case it recurses into itself.\n>\n>   - Or it may see a submodule, and in that case it recurses via\n>     `grep_submodule()` which then again calls `grep_tree()`.\n>\n> In the latter case we continue grepping in the submodule repository's\n> object graph. Historically this used to work by linking that submodule's\n> object directory into the main object database. But in 93cd344e34f\n> (builtin/grep: stop registering submodule ODB as source, 2026-09-11) we\n> stopped doing that and instead expect to read submodule objects via the\n> submodule repository.\n>\n> This logic is broken though: while we indeed know to read blobs via the\n> submodule's object database, we didn't adapt trees in the same way. The\n> result is that Git fails to read the object:\n>\n>     $ git grep --recurse-submodules -e hello HEAD\n>     fatal: unable to read tree (935cff11a11f8734cf4e0d40955d1e0e5c12038e)\n>\n> This went unnoticed because our tests only cover submodules that contain\n> files at the top level.\n\nShould we mention this is a regression in 2.56?\n\n> Fix this by reading nested trees via `opt->repo`, which is set to the\n> submodule's repository via `grep_submodule()`.\n>\n> Reported-by: Root <rootminus0x1@gmail.com>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/grep.c                     |  2 +-\n>  t/t7814-grep-recurse-submodules.sh | 23 +++++++++++++++++++++++\n>  2 files changed, 24 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/grep.c b/builtin/grep.c\n> index b045f8a488d..22fb365eeac 100644\n> --- a/builtin/grep.c\n> +++ b/builtin/grep.c\n> @@ -653,7 +653,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,\n>  \t\t\tvoid *data;\n>  \t\t\tsize_t size;\n>  \n> -\t\t\tdata = odb_read_object(the_repository->objects,\n> +\t\t\tdata = odb_read_object(repo->objects,\n\nDerp... What simple (in hindsight) fix.\n\n>  \t\t\t\t\t       &entry.oid, &type, &size);\n>  \t\t\tif (!data)\n>  \t\t\t\tdie(_(\"unable to read tree (%s)\"),\n> diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh\n> index 3d149d34c15..f51c5181805 100755\n> --- a/t/t7814-grep-recurse-submodules.sh\n> +++ b/t/t7814-grep-recurse-submodules.sh\n> @@ -631,4 +631,27 @@ test_expect_success 'check scope of core.useReplaceRefs' '\n>  \tgit -C base grep --cached --recurse-submodules C\n>  '\n>  \n> +test_expect_success 'grep --recurse-submodules with tree-ish and nested directory' '\n> +\trm -rf sub super &&\n\nSmall nit, shall we use test_when_finished here?\n\n> +\tgit init sub &&\n> +\tmkdir sub/dir &&\n> +\techo hello >sub/top &&\n> +\techo hello >sub/dir/nested &&\n> +\tgit -C sub add top dir/nested &&\n> +\tgit -C sub commit --message \"sub\" &&\n> +\n> +\ttest_config_global protocol.file.allow always &&\n> +\tgit init super &&\n> +\tgit -C super submodule add \"$(pwd)/sub\" sub &&\n> +\tgit -C super commit --message \"super\" &&\n> +\n> +\tcat >expect <<-\\EOF &&\n> +\tHEAD:sub/dir/nested:hello\n> +\tHEAD:sub/top:hello\n> +\tEOF\n> +\tgit -C super grep --recurse-submodules -e hello HEAD >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_done\n\nVerified the test fails without the fix above.\n\nAll changes look good.\n\n> -- \n> 2.56.0.341.g393f52fd96.dirty\n\nThe other two patches make sense to me as well. Seems worthwhile to\ninclude them even if they aren't directly covered with a test.\n\n-- \nLaters,\nToon\n\n"},{"id":"557297","messageId":"aunAR67MHppLt--N@pks.im","threadId":"66711","inReplyTo":"87fqxnyx3s.fsf@dev.null.iotcl.com.invalid","subject":"Re: [PATCH 1/3] builtin/grep: read submodule trees via their repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T11:06:47Z","receivedAt":"2026-11-03T11:06:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Oct 30, 2026 at 05:25:11PM +0100, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The function `grep_tree()` searches for a specific string recursively in\n> > a tree. It can recurse in two different ways:\n> >\n> >   - It may either see a subtree, in which case it recurses into itself.\n> >\n> >   - Or it may see a submodule, and in that case it recurses via\n> >     `grep_submodule()` which then again calls `grep_tree()`.\n> >\n> > In the latter case we continue grepping in the submodule repository's\n> > object graph. Historically this used to work by linking that submodule's\n> > object directory into the main object database. But in 93cd344e34f\n> > (builtin/grep: stop registering submodule ODB as source, 2026-09-11) we\n> > stopped doing that and instead expect to read submodule objects via the\n> > submodule repository.\n> >\n> > This logic is broken though: while we indeed know to read blobs via the\n> > submodule's object database, we didn't adapt trees in the same way. The\n> > result is that Git fails to read the object:\n> >\n> >     $ git grep --recurse-submodules -e hello HEAD\n> >     fatal: unable to read tree (935cff11a11f8734cf4e0d40955d1e0e5c12038e)\n> >\n> > This went unnoticed because our tests only cover submodules that contain\n> > files at the top level.\n> \n> Should we mention this is a regression in 2.56?\n\nFair, can do.\n\n> > diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh\n> > index 3d149d34c15..f51c5181805 100755\n> > --- a/t/t7814-grep-recurse-submodules.sh\n> > +++ b/t/t7814-grep-recurse-submodules.sh\n> > @@ -631,4 +631,27 @@ test_expect_success 'check scope of core.useReplaceRefs' '\n> >  \tgit -C base grep --cached --recurse-submodules C\n> >  '\n> >  \n> > +test_expect_success 'grep --recurse-submodules with tree-ish and nested directory' '\n> > +\trm -rf sub super &&\n> \n> Small nit, shall we use test_when_finished here?\n\nNormally I would, but in this case it would have felt weird to have \"rm\n-rf sub super && test_when_finished \"rm -rf sub super\". We have to\ndelete the old repos because they exist already, so arguably the correct\nlocation to add the `test_when_finished` would have been that preceding\ntest. But I'd propose that this can be done at a later time?\n\nPatrick\n\n"},{"id":"557307","messageId":"20261103-pks-grep-recurse-submodule-v2-0-b00e3b3dc865@pks.im","threadId":"66711","inReplyTo":"20261030-pks-grep-recurse-submodule-v1-0-d4eb5c87d8fb@pks.im","subject":"[PATCH v2 0/3] builtin/grep: fix reading objects from wrong repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T12:43:19Z","receivedAt":"2026-11-03T12:43:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis small patch series fixes the bug reported in [1], where doing `git\ngrep --recurse-submodules` with nested submodule trees fails to read the\ntrees. The only patch needed here is the first one, the other patches\nare only belt-and-suspenders as I've been checking whether we use the\nwrong repository anywhere else.\n\nAt the end of this patch series we only use `the_repository` in\n`cmd_grep()` itself. Ideally we'd adapt \"builtin/grep.c\" to get rid of\n`the_repository` altogether, but unfortunately it's a bit on the\ninvolved side as we also have to worry about the case where git-grep(1)\nis executed outside of a repository with \"--no-index\". So I skipped that\nrefactoring for now.\n\nThe patch is based on v2.56.0 so that it can be easily backported.\n\nChanges in v2:\n  - Mention that this was released as part of Git 2.56.0.\n  - Link to v1: https://patch.msgid.link/20261030-pks-grep-recurse-submodule-v1-0-d4eb5c87d8fb@pks.im\n\nThanks!\n\nPatrick\n\n[1]: <CAMtVYEjWd2XwJpXdk3PV2jbNdoOaqS3x+08EMu5byy55RxfRmA@mail.gmail.com>\n\n---\nPatrick Steinhardt (3):\n      builtin/grep: read submodule trees via their repository\n      builtin/grep: read sparse directory trees via passed-in repository\n      builtin/grep: read \".gitmodules\" via passed-in repository\n\n builtin/grep.c                     |  6 +++---\n t/t7814-grep-recurse-submodules.sh | 23 +++++++++++++++++++++++\n 2 files changed, 26 insertions(+), 3 deletions(-)\n\nRange-diff versus v1:\n\n1:  e9ffa0cdee5 ! 1:  46d165815b5 builtin/grep: read submodule trees via their repository\n    @@ Commit message\n         In the latter case we continue grepping in the submodule repository's\n         object graph. Historically this used to work by linking that submodule's\n         object directory into the main object database. But in 93cd344e34f\n    -    (builtin/grep: stop registering submodule ODB as source, 2026-09-11) we\n    -    stopped doing that and instead expect to read submodule objects via the\n    -    submodule repository.\n    +    (builtin/grep: stop registering submodule ODB as source, 2026-09-11),\n    +    which was released as part of Git 2.56, we stopped doing that and\n    +    instead expect to read submodule objects via the submodule repository.\n     \n         This logic is broken though: while we indeed know to read blobs via the\n         submodule's object database, we didn't adapt trees in the same way. The\n2:  dfe45977b15 = 2:  f7516e29f68 builtin/grep: read sparse directory trees via passed-in repository\n3:  4b914d9d07d = 3:  dc19e6589d4 builtin/grep: read \".gitmodules\" via passed-in repository\n\n---\nbase-commit: a018953688f1b10bddf91bff8747068f5f4746a4\nchange-id: 20261030-pks-grep-recurse-submodule-20177cc7794c\n\n\n"},{"id":"557308","messageId":"20261103-pks-grep-recurse-submodule-v2-1-b00e3b3dc865@pks.im","threadId":"66711","inReplyTo":"20261103-pks-grep-recurse-submodule-v2-0-b00e3b3dc865@pks.im","subject":"[PATCH v2 1/3] builtin/grep: read submodule trees via their repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T12:43:20Z","receivedAt":"2026-11-03T12:43:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `grep_tree()` searches for a specific string recursively in\na tree. It can recurse in two different ways:\n\n  - It may either see a subtree, in which case it recurses into itself.\n\n  - Or it may see a submodule, and in that case it recurses via\n    `grep_submodule()` which then again calls `grep_tree()`.\n\nIn the latter case we continue grepping in the submodule repository's\nobject graph. Historically this used to work by linking that submodule's\nobject directory into the main object database. But in 93cd344e34f\n(builtin/grep: stop registering submodule ODB as source, 2026-09-11),\nwhich was released as part of Git 2.56, we stopped doing that and\ninstead expect to read submodule objects via the submodule repository.\n\nThis logic is broken though: while we indeed know to read blobs via the\nsubmodule's object database, we didn't adapt trees in the same way. The\nresult is that Git fails to read the object:\n\n    $ git grep --recurse-submodules -e hello HEAD\n    fatal: unable to read tree (935cff11a11f8734cf4e0d40955d1e0e5c12038e)\n\nThis went unnoticed because our tests only cover submodules that contain\nfiles at the top level.\n\nFix this by reading nested trees via `opt->repo`, which is set to the\nsubmodule's repository via `grep_submodule()`.\n\nReported-by: Root <rootminus0x1@gmail.com>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c                     |  2 +-\n t/t7814-grep-recurse-submodules.sh | 23 +++++++++++++++++++++++\n 2 files changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex b045f8a488d..22fb365eeac 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -653,7 +653,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,\n \t\t\tvoid *data;\n \t\t\tsize_t size;\n \n-\t\t\tdata = odb_read_object(the_repository->objects,\n+\t\t\tdata = odb_read_object(repo->objects,\n \t\t\t\t\t       &entry.oid, &type, &size);\n \t\t\tif (!data)\n \t\t\t\tdie(_(\"unable to read tree (%s)\"),\ndiff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh\nindex 3d149d34c15..f51c5181805 100755\n--- a/t/t7814-grep-recurse-submodules.sh\n+++ b/t/t7814-grep-recurse-submodules.sh\n@@ -631,4 +631,27 @@ test_expect_success 'check scope of core.useReplaceRefs' '\n \tgit -C base grep --cached --recurse-submodules C\n '\n \n+test_expect_success 'grep --recurse-submodules with tree-ish and nested directory' '\n+\trm -rf sub super &&\n+\n+\tgit init sub &&\n+\tmkdir sub/dir &&\n+\techo hello >sub/top &&\n+\techo hello >sub/dir/nested &&\n+\tgit -C sub add top dir/nested &&\n+\tgit -C sub commit --message \"sub\" &&\n+\n+\ttest_config_global protocol.file.allow always &&\n+\tgit init super &&\n+\tgit -C super submodule add \"$(pwd)/sub\" sub &&\n+\tgit -C super commit --message \"super\" &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tHEAD:sub/dir/nested:hello\n+\tHEAD:sub/top:hello\n+\tEOF\n+\tgit -C super grep --recurse-submodules -e hello HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\n-- \n2.56.0.500.g5d06dc02cd.dirty\n\n\n"},{"id":"557309","messageId":"20261103-pks-grep-recurse-submodule-v2-2-b00e3b3dc865@pks.im","threadId":"66711","inReplyTo":"20261103-pks-grep-recurse-submodule-v2-0-b00e3b3dc865@pks.im","subject":"[PATCH v2 2/3] builtin/grep: read sparse directory trees via passed-in repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T12:43:21Z","receivedAt":"2026-11-03T12:43:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When iterating through the index in `grep_cache()` we may encounter\nsparse directory entries. In that case we read the tree object they\npoint to so that we can recurse into it via `grep_tree()`. We do so via\n`the_repository` though, even though `grep_cache()` may be called for a\nsubmodule via `grep_submodule()`.\n\nThis issue is similar to the one fixed in the preceding commit, where\n`grep_tree()` didn't use the correct repository. But in this case here\nthe issue cannot be triggered as we call `repo_read_gitmodules()` on the\nsubmodule repository, and that function will cause us to read its index.\nBut because we only ever set `command_requires_full_index` for the\ntop-level repository we will convert the submodule's index into a full\nindex, and consequently we will never see sparse directory entries. This\nis also documented as such in a NEEDSWORK comment in `grep_submodule()`.\n\nRegardless of that the code is still wrong, and it may very easily turn\ninto a bug once we start to handle sparse indices for submodules. Adapt\nit to use the correct repository to preempt such a future bug.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 22fb365eeac..cb518d8155e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -560,7 +560,7 @@ static int grep_cache(struct grep_opt *opt,\n \t\t\tvoid *data;\n \t\t\tsize_t size;\n \n-\t\t\tdata = odb_read_object(the_repository->objects, &ce->oid,\n+\t\t\tdata = odb_read_object(repo->objects, &ce->oid,\n \t\t\t\t\t       &type, &size);\n \t\t\tif (!data)\n \t\t\t\tdie(_(\"unable to read tree %s\"), oid_to_hex(&ce->oid));\n\n-- \n2.56.0.500.g5d06dc02cd.dirty\n\n\n"},{"id":"557310","messageId":"20261103-pks-grep-recurse-submodule-v2-3-b00e3b3dc865@pks.im","threadId":"66711","inReplyTo":"20261103-pks-grep-recurse-submodule-v2-0-b00e3b3dc865@pks.im","subject":"[PATCH v2 3/3] builtin/grep: read \".gitmodules\" via passed-in repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T12:43:22Z","receivedAt":"2026-11-03T12:43:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When grepping through a list of objects with \"--recurse-submodules\", we\ncall `gitmodules_config_oid()` to load gitmodules for the specific\nrevision. But instead of passing `opt->repo` we pass `the_repository` to\nthat function, which is somewhat fishy.\n\nThis is again not a bug because `grep_objects()` is only ever called by\n`cmd_grep()`, and consequently we know that `opt->repo` is always going\nto be set to `the_repository`. But it's a code smell regardless of that,\nso adapt the code to use `opt->repo` instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex cb518d8155e..22f0a1b0d0e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -883,7 +883,7 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,\n \t\tif (recurse_submodules) {\n \t\t\tsubmodule_free(opt->repo);\n \t\t\tobj_read_lock();\n-\t\t\tgitmodules_config_oid(the_repository, &real_obj->oid);\n+\t\t\tgitmodules_config_oid(opt->repo, &real_obj->oid);\n \t\t\tobj_read_unlock();\n \t\t}\n \t\tif (grep_object(opt, pathspec, real_obj, list->objects[i].name,\n\n-- \n2.56.0.500.g5d06dc02cd.dirty\n\n\n"}]}