{"thread":{"id":"66246","subject":"[PATCH 00/12] odb: stop registering in-memory sources","startedAt":"2026-09-01T11:09:35Z","lastAt":"2026-09-11T05:52:23Z","messageCount":31,"participants":["Patrick Steinhardt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"551645","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":null,"subject":"[PATCH 00/12] odb: stop registering in-memory sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:08:59Z","receivedAt":"2026-09-01T11:09:35Z","isPatch":true,"body":"Hi,\n\nthe object database has a list of sources that is used for two\ndifferent purposes:\n\n  - We use it to track the list of alternates.\n\n  - We use it to track temporary in-memory sources that we create for\n    various purposes. Most importantly, this is used to link object\n    database sources from submodules into the main store.\n\nThis dual-use is quite awkward, as it mixes two different levels of\nconcerns and thus as a consequence makes both harder to reason about.\nIt's also a source of bugs: we make assumptions about the ordering of\nsources all over the place, and we furthermore assume in other places\nthat the sources only contain alternates in the first place. I don't\nthink this surfaces in the form of real bugs, but I've long disliked\nthis dual-use.\n\nFurthermore, we want to migrate handling of alternates into the \"files\"\nbackend itself in a subsequent patch series. This is most importantly to\nfix a performance regression by making the backend own all of its\nalternates, but it also fixes a couple of longer-standing design issues\nthat I've been struggling with [1].\n\nMost importantly though: this whole machinery is not even needed at all.\nA couple years ago we have already refactored our codebase so that\nsubmodule sources don't even have to be linked into the main object\ndatabase anymore. And all the other use cases where we link sources into\nthe main object database can be trivially converted, too.\n\nSo this patch series does exactly that: it removes the mechanism to link\nad-hoc sources into the object database entirely. This ensures that the\nlist of sources is exactly the list of alternates, and that makes it\neasier to move them into the \"files\" backend in a subsequent patch\nseries.\n\nThere is one exception though: creating transactions still creates a\ntemporary quarantine directory. This mechanism is left as-is for now,\nbut as it's an implementation detail of the \"files\" backend anyway\nthat's not conflicting with our above stated goals.\n\nThis series is built on top of 1630431f32 (The 21st batch, 2026-08-31)\nwith ty/repository-fetch-if-missing at 508ec9837c (repository: move\nfetch_if_missing into struct repository, 2026-08-15) merged into it.\nThere's still two merge conflicts, but these are trivial to resolve: in\n\"odb.c\" and \"odb.h\" you simply remove both ours and theirs, and in\n\"builtin/multi-pack-index.c\" you only need to munge the parameters a\nbit. I've attached a patch below that shows the resolution.\n\nThanks!\n\nPatrick\n\n[1]: <amLgMqkqxR8mKIbT@pks.im>\n\ndiff --cc builtin/multi-pack-index.c\nindex dc8561a83b,84d2467cc7..0000000000\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@@ -230,11 -224,10 +230,12 @@@ static int cmd_multi_pack_index_write(i\n  \n  \t}\n  \n- \tret = write_midx_file(source, opts.preferred_pack,\n- \t\t\t      opts.refs_snapshot, opts.flags);\n -\tret = write_midx_file(source->packed, opts.preferred_pack,\n++\tret = write_midx_file(source, opts.preferred_pack,\n+ \t\t\t      opts.refs_snapshot, opts.incremental_base,\n+ \t\t\t      opts.flags);\n  \n +\tif (created)\n +\t\todb_source_free(&source->base);\n  \tfree(opts.refs_snapshot);\n  \treturn ret;\n  }\ndiff --cc odb.c\nindex 5fe081496f,0cf99efe94..0000000000\n--- a/odb.c\n+++ b/odb.c\n@@@ -238,15 -239,12 +239,6 @@@ static struct odb_source *odb_add_alter\n  \treturn alternate;\n  }\n  \n- void odb_add_to_alternates_file(struct object_database *odb,\n- \t\t\t\tconst char *dir)\n -struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n -\t\t\t\t\t\tconst char *dir)\n--{\n- \tint ret = odb_source_write_alternate(odb->sources, dir);\n- \tif (ret < 0)\n- \t\tdie(NULL);\n- \todb_add_alternate_recursively(odb, dir, 0);\n -\treturn odb_add_alternate_recursively(odb, dir, 0);\n--}\n--\n  struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,\n  \t\t\t\t\t\t    const char *dir, int will_destroy,\n  \t\t\t\t\t\t    struct odb_source **prev_source)\ndiff --cc odb.h\nindex 9025239df5,862f373467..0000000000\n--- a/odb.h\n+++ b/odb.h\n@@@ -251,13 -302,14 +288,6 @@@ int odb_mkstemp(struct object_database \n   */\n  int odb_has_alternates(struct object_database *odb);\n  \n--/*\n-  * Add the directory to the on-disk alternates file; the new entry will also\n-  * take effect in the current process.\n - * Add the directory to the in-memory list of alternate sources (along with any\n - * recursive alternates it points to), but do not modify the on-disk alternates\n - * file.\n-- */\n- void odb_add_to_alternates_file(struct object_database *odb,\n- \t\t\t\tconst char *dir);\n -struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n -\t\t\t\t\t\tconst char *dir);\n--\n  /*\n   * Read an object from the database. Returns the object data and assigns object\n   * type and size to the `type` and `size` pointers, if these pointers are\n\n---\nPatrick Steinhardt (12):\n      cache-tree: remove dependency on `the_repository`\n      submodule-config: remove uses of `the_repository`\n      submodule-config: stop using `the_hash_algo`\n      submodule-config: stop registering submodule sources\n      builtin/grep: stop registering submodule ODB as source\n      odb: remove infrastructure to register submodule sources\n      tmp-objdir: drop unused function to register alternate\n      odb/packed: fix memory leaks when freeing source\n      builtin/multi-pack-index: refuse unknown sources with \"--object-dir=\"\n      t/helper: adapt read-midx to not link ad-hoc source anymore\n      t/helper: stop registering alternates in \"ref-store\" command\n      odb: remove the ability to link sources ad-hoc\n\n builtin/checkout.c                     |  2 +-\n builtin/commit.c                       |  2 +-\n builtin/fetch.c                        |  2 +-\n builtin/grep.c                         | 28 +++--------\n builtin/multi-pack-index.c             |  3 +-\n builtin/submodule--helper.c            |  8 ++--\n cache-tree.c                           | 88 ++++++++++++++++++----------------\n cache-tree.h                           |  7 +--\n odb.c                                  | 42 ----------------\n odb.h                                  | 22 ---------\n odb/source-packed.c                    |  1 +\n read-cache-ll.h                        |  5 +-\n read-cache.c                           |  9 ++--\n sequencer.c                            |  2 +-\n sparse-index.c                         |  2 +-\n submodule-config.c                     | 59 ++++++++++++-----------\n submodule-config.h                     | 12 +++--\n submodule.c                            |  2 +-\n t/README                               |  7 ---\n t/helper/test-read-midx.c              | 43 ++++++++++++-----\n t/helper/test-ref-store.c              |  8 ----\n t/helper/test-submodule.c              |  4 +-\n t/t5319-multi-pack-index.sh            |  9 ++--\n t/t5526-fetch-submodules.sh            |  3 --\n t/t5531-deep-submodule-push.sh         |  3 --\n t/t5545-push-options.sh                |  3 --\n t/t5572-pull-submodule.sh              |  3 --\n t/t6437-submodule-merge.sh             |  3 --\n t/t7418-submodule-sparse-gitmodules.sh |  3 --\n t/t7814-grep-recurse-submodules.sh     |  3 --\n tmp-objdir.c                           |  5 --\n tmp-objdir.h                           |  6 ---\n unpack-trees.c                         |  9 ++--\n 33 files changed, 161 insertions(+), 247 deletions(-)\n\n\n---\nbase-commit: e5d60560f61f520e9ea350645a6cc9770b0f1607\nchange-id: 20260811-pks-odb-registering-in-memory-sources-88648cd95735\n\n"},{"id":"551646","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-1-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 01/12] cache-tree: remove dependency on `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:00Z","receivedAt":"2026-09-01T11:09:37Z","isPatch":true,"body":"The \"cache-tree\" subsystem still depends on `the_repository`. Adapt it\nto instead use repositories provided via the context, either as a new\nparameter or the one passed in via `struct index_state`.\n\nBesides getting rid of `the_repository`, this also removes the last\ndependency on registering submodule sources with the main object\ndatabase. When reading gitmodules from a submodule's index we implicitly\nread that object via `the_repository`'s object database, which is of\ncourse wrong. This works though because we would then register the\nsubmodule's object database with the main object database, but a later\npatch is going to get rid of that mechanism.\n\nYou can verify that we indeed no longer depend on this mechanism by\nrunning tests with `GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=true`. Without\nthis patch we fail in t1092, with this patch we never register submodule\nobject databases anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/checkout.c |  2 +-\n builtin/commit.c   |  2 +-\n cache-tree.c       | 88 +++++++++++++++++++++++++++++-------------------------\n cache-tree.h       |  7 +++--\n read-cache-ll.h    |  5 ++--\n read-cache.c       |  9 +++---\n sequencer.c        |  2 +-\n sparse-index.c     |  2 +-\n unpack-trees.c     |  9 +++---\n 9 files changed, 68 insertions(+), 58 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 55e3a89a85..60d106f4d5 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -921,7 +921,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t}\n \t}\n \n-\tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n+\tif (!cache_tree_fully_valid(the_repository, the_repository->index->cache_tree))\n \t\tcache_tree_update(the_repository->index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);\n \n \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 28f6174503..02df1c2feb 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -484,7 +484,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\t\t\t       LOCK_DIE_ON_ERROR);\n \t\trefresh_cache_or_die(refresh_flags);\n \t\tif (the_repository->index->cache_changed\n-\t\t    || !cache_tree_fully_valid(the_repository->index->cache_tree))\n+\t\t    || !cache_tree_fully_valid(the_repository, the_repository->index->cache_tree))\n \t\t\tcache_tree_update(the_repository->index, WRITE_TREE_SILENT);\n \t\tif (write_locked_index(the_repository->index, &index_lock,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\ndiff --git a/cache-tree.c b/cache-tree.c\nindex a220372a42..6e4d82f3e8 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\n@@ -275,28 +274,30 @@ static void discard_unused_subtrees(struct cache_tree *it)\n \t}\n }\n \n-int cache_tree_fully_valid(struct cache_tree *it)\n+int cache_tree_fully_valid(struct repository *repo, struct cache_tree *it)\n {\n \tint i;\n \tif (!it)\n \t\treturn 0;\n \tif (it->entry_count < 0 ||\n-\t    !odb_has_object(the_repository->objects, &it->oid,\n+\t    !odb_has_object(repo->objects, &it->oid,\n \t\t\t    ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))\n \t\treturn 0;\n \tfor (i = 0; i < it->subtree_nr; i++) {\n-\t\tif (!cache_tree_fully_valid(it->down[i]->cache_tree))\n+\t\tif (!cache_tree_fully_valid(repo, it->down[i]->cache_tree))\n \t\t\treturn 0;\n \t}\n \treturn 1;\n }\n \n-static int must_check_existence(const struct cache_entry *ce)\n+static int must_check_existence(const struct cache_entry *ce, void *cb_data)\n {\n-\treturn !(repo_has_promisor_remote(the_repository) && ce_skip_worktree(ce));\n+\tstruct repository *repo = cb_data;\n+\treturn !(repo_has_promisor_remote(repo) && ce_skip_worktree(ce));\n }\n \n-static int update_one(struct cache_tree *it,\n+static int update_one(struct repository *repo,\n+\t\t      struct cache_tree *it,\n \t\t      struct cache_entry **cache,\n \t\t      int entries,\n \t\t      const char *base,\n@@ -334,7 +335,7 @@ static int update_one(struct cache_tree *it,\n \t}\n \n \tif (0 <= it->entry_count &&\n-\t    odb_has_object(the_repository->objects, &it->oid,\n+\t    odb_has_object(repo->objects, &it->oid,\n \t\t\t   ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))\n \t\treturn it->entry_count;\n \n@@ -375,7 +376,8 @@ static int update_one(struct cache_tree *it,\n \t\tsub = find_subtree(it, path + baselen, sublen, 1);\n \t\tif (!sub->cache_tree)\n \t\t\tsub->cache_tree = cache_tree();\n-\t\tsubcnt = update_one(sub->cache_tree,\n+\t\tsubcnt = update_one(repo,\n+\t\t\t\t    sub->cache_tree,\n \t\t\t\t    cache + i, entries - i,\n \t\t\t\t    path,\n \t\t\t\t    baselen + sublen + 1,\n@@ -439,10 +441,10 @@ static int update_one(struct cache_tree *it,\n \t\t}\n \n \t\tce_missing_ok = mode == S_IFGITLINK || missing_ok ||\n-\t\t\t!must_check_existence(ce);\n+\t\t\t!must_check_existence(ce, repo);\n \t\tif (is_null_oid(oid) ||\n \t\t    (!ce_missing_ok &&\n-\t\t     !odb_has_object(the_repository->objects, oid,\n+\t\t     !odb_has_object(repo->objects, oid,\n \t\t\t\t     ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))) {\n \t\t\tstrbuf_release(&buffer);\n \t\t\tif (expected_missing)\n@@ -474,12 +476,12 @@ static int update_one(struct cache_tree *it,\n \t\t/*\n \t\t * \"sub\" can be an empty tree if all subentries are i-t-a.\n \t\t */\n-\t\tif (contains_ita && is_empty_tree_oid(oid, the_repository->hash_algo))\n+\t\tif (contains_ita && is_empty_tree_oid(oid, repo->hash_algo))\n \t\t\tcontinue;\n \n \t\tstrbuf_grow(&buffer, entlen + 100);\n \t\tstrbuf_addf(&buffer, \"%o %.*s%c\", mode, entlen, path + baselen, '\\0');\n-\t\tstrbuf_add(&buffer, oid->hash, the_hash_algo->rawsz);\n+\t\tstrbuf_add(&buffer, oid->hash, repo->hash_algo->rawsz);\n \n #if DEBUG_CACHE_TREE\n \t\tfprintf(stderr, \"cache-tree update-one %o %.*s\\n\",\n@@ -489,16 +491,16 @@ static int update_one(struct cache_tree *it,\n \n \tif (repair) {\n \t\tstruct object_id oid;\n-\t\thash_object_file(the_hash_algo, buffer.buf, buffer.len,\n+\t\thash_object_file(repo->hash_algo, buffer.buf, buffer.len,\n \t\t\t\t OBJ_TREE, &oid);\n-\t\tif (odb_has_object(the_repository->objects, &oid, ODB_HAS_OBJECT_RECHECK_PACKED))\n+\t\tif (odb_has_object(repo->objects, &oid, ODB_HAS_OBJECT_RECHECK_PACKED))\n \t\t\toidcpy(&it->oid, &oid);\n \t\telse\n \t\t\tto_invalidate = 1;\n \t} else if (dryrun) {\n-\t\thash_object_file(the_hash_algo, buffer.buf, buffer.len,\n+\t\thash_object_file(repo->hash_algo, buffer.buf, buffer.len,\n \t\t\t\t OBJ_TREE, &it->oid);\n-\t} else if (odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len, OBJ_TREE,\n+\t} else if (odb_write_object_ext(repo->objects, buffer.buf, buffer.len, OBJ_TREE,\n \t\t\t\t\t&it->oid, NULL, flags & WRITE_TREE_SILENT ? ODB_WRITE_OBJECT_SILENT : 0)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn -1;\n@@ -516,7 +518,7 @@ static int update_one(struct cache_tree *it,\n \n int cache_tree_update(struct index_state *istate, int flags)\n {\n-\tint inflight = !!the_repository->objects->transaction;\n+\tint inflight = !!istate->repo->objects->transaction;\n \tstruct odb_transaction *transaction;\n \tint skip, i;\n \n@@ -528,14 +530,14 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \n-\tif (!(flags & WRITE_TREE_MISSING_OK) && repo_has_promisor_remote(the_repository))\n-\t\tprefetch_cache_entries(istate, must_check_existence);\n+\tif (!(flags & WRITE_TREE_MISSING_OK) && repo_has_promisor_remote(istate->repo))\n+\t\tprefetch_cache_entries(istate, must_check_existence, istate->repo);\n \n \ttrace_performance_enter();\n \ttrace2_region_enter(\"cache_tree\", \"update\", istate->repo);\n \tif (!inflight)\n-\t\todb_transaction_begin_or_die(the_repository->objects, &transaction, 0);\n-\ti = update_one(istate->cache_tree, istate->cache, istate->cache_nr,\n+\t\todb_transaction_begin_or_die(istate->repo->objects, &transaction, 0);\n+\ti = update_one(istate->repo, istate->cache_tree, istate->cache, istate->cache_nr,\n \t\t       \"\", 0, &skip, flags);\n \tif (!inflight)\n \t\todb_transaction_commit_and_finalize_or_die(transaction);\n@@ -547,7 +549,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \treturn 0;\n }\n \n-static void write_one(struct strbuf *buffer, struct cache_tree *it,\n+static void write_one(struct repository *repo,\n+\t\t      struct strbuf *buffer, struct cache_tree *it,\n \t\t      const char *path, int pathlen)\n {\n \tint i;\n@@ -573,7 +576,7 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it,\n #endif\n \n \tif (0 <= it->entry_count) {\n-\t\tstrbuf_add(buffer, it->oid.hash, the_hash_algo->rawsz);\n+\t\tstrbuf_add(buffer, it->oid.hash, repo->hash_algo->rawsz);\n \t}\n \tfor (i = 0; i < it->subtree_nr; i++) {\n \t\tstruct cache_tree_sub *down = it->down[i];\n@@ -583,15 +586,16 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it,\n \t\t\t\t\t     prev->name, prev->namelen) <= 0)\n \t\t\t\tdie(\"fatal - unsorted cache subtree\");\n \t\t}\n-\t\twrite_one(buffer, down->cache_tree, down->name, down->namelen);\n+\t\twrite_one(repo, buffer, down->cache_tree, down->name, down->namelen);\n \t}\n }\n \n-void cache_tree_write(struct strbuf *sb, struct cache_tree *root)\n+void cache_tree_write(struct repository *repo,\n+\t\t      struct strbuf *sb, struct cache_tree *root)\n {\n-\ttrace2_region_enter(\"cache_tree\", \"write\", the_repository);\n-\twrite_one(sb, root, \"\", 0);\n-\ttrace2_region_leave(\"cache_tree\", \"write\", the_repository);\n+\ttrace2_region_enter(\"cache_tree\", \"write\", repo);\n+\twrite_one(repo, sb, root, \"\", 0);\n+\ttrace2_region_leave(\"cache_tree\", \"write\", repo);\n }\n \n static int parse_int(const char **ptr, unsigned long *len_p, int *out)\n@@ -625,13 +629,14 @@ static int parse_int(const char **ptr, unsigned long *len_p, int *out)\n \treturn 0;\n }\n \n-static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n+static struct cache_tree *read_one(struct repository *repo,\n+\t\t\t\t   const char **buffer, unsigned long *size_p)\n {\n \tconst char *buf = *buffer;\n \tunsigned long size = *size_p;\n \tstruct cache_tree *it;\n \tint i, subtree_nr;\n-\tconst unsigned rawsz = the_hash_algo->rawsz;\n+\tconst unsigned rawsz = repo->hash_algo->rawsz;\n \n \tit = NULL;\n \t/* skip name, but make sure name exists */\n@@ -658,7 +663,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n \t\tif (size < rawsz)\n \t\t\tgoto free_return;\n \t\toidread(&it->oid, (const unsigned char *)buf,\n-\t\t\tthe_repository->hash_algo);\n+\t\t\trepo->hash_algo);\n \t\tbuf += rawsz;\n \t\tsize -= rawsz;\n \t}\n@@ -686,7 +691,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n \t\tstruct cache_tree_sub *subtree;\n \t\tconst char *name = buf;\n \n-\t\tsub = read_one(&buf, &size);\n+\t\tsub = read_one(repo, &buf, &size);\n \t\tif (!sub)\n \t\t\tgoto free_return;\n \t\tsubtree = cache_tree_sub(it, name);\n@@ -703,16 +708,17 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n \treturn NULL;\n }\n \n-struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)\n+struct cache_tree *cache_tree_read(struct repository *repo,\n+\t\t\t\t   const char *buffer, unsigned long size)\n {\n \tstruct cache_tree *result;\n \n \tif (buffer[0])\n \t\treturn NULL; /* not the whole tree */\n \n-\ttrace2_region_enter(\"cache_tree\", \"read\", the_repository);\n-\tresult = read_one(&buffer, &size);\n-\ttrace2_region_leave(\"cache_tree\", \"read\", the_repository);\n+\ttrace2_region_enter(\"cache_tree\", \"read\", repo);\n+\tresult = read_one(repo, &buffer, &size);\n+\ttrace2_region_leave(\"cache_tree\", \"read\", repo);\n \n \treturn result;\n }\n@@ -775,7 +781,7 @@ struct tree *write_in_core_index_as_tree(struct repository *repo,\n \tint was_valid, ret;\n \n \twas_valid = index_state->cache_tree &&\n-\t\t    cache_tree_fully_valid(index_state->cache_tree);\n+\t\t    cache_tree_fully_valid(repo, index_state->cache_tree);\n \n \tret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL);\n \tif (ret == WRITE_TREE_UNMERGED_INDEX) {\n@@ -803,7 +809,7 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \thold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);\n \n \tentries = read_index_from(index_state, index_path,\n-\t\t\t\t  repo_get_git_dir(the_repository));\n+\t\t\t\t  repo_get_git_dir(index_state->repo));\n \tif (entries < 0) {\n \t\tret = WRITE_TREE_UNREADABLE_INDEX;\n \t\tgoto out;\n@@ -811,7 +817,7 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \n \twas_valid = !(flags & WRITE_TREE_IGNORE_CACHE_TREE) &&\n \t\t    index_state->cache_tree &&\n-\t\t    cache_tree_fully_valid(index_state->cache_tree);\n+\t\t    cache_tree_fully_valid(index_state->repo, index_state->cache_tree);\n \n \tret = write_index_as_tree_internal(oid, index_state, was_valid, flags,\n \t\t\t\t\t   prefix);\n@@ -859,7 +865,7 @@ static void prime_cache_tree_rec(struct repository *r,\n \t\t\tstruct cache_tree_sub *sub;\n \t\t\tstruct tree *subtree = lookup_tree(r, &entry.oid);\n \n-\t\t\tif (repo_parse_tree(the_repository, subtree) < 0)\n+\t\t\tif (repo_parse_tree(r, subtree) < 0)\n \t\t\t\texit(128);\n \t\t\tsub = cache_tree_sub(it, entry.path);\n \t\t\tsub->cache_tree = cache_tree();\ndiff --git a/cache-tree.h b/cache-tree.h\nindex f8bddae523..5d49308de9 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -28,10 +28,11 @@ struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);\n \n int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen);\n \n-void cache_tree_write(struct strbuf *, struct cache_tree *root);\n-struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);\n+void cache_tree_write(struct repository *repo, struct strbuf *, struct cache_tree *root);\n+struct cache_tree *cache_tree_read(struct repository *repo,\n+\t\t\t\t   const char *buffer, unsigned long size);\n \n-int cache_tree_fully_valid(struct cache_tree *);\n+int cache_tree_fully_valid(struct repository *, struct cache_tree *);\n int cache_tree_update(struct index_state *, int);\n int cache_tree_verify(struct repository *, struct index_state *);\n \ndiff --git a/read-cache-ll.h b/read-cache-ll.h\nindex 8eb266cfd1..066dd8bc3b 100644\n--- a/read-cache-ll.h\n+++ b/read-cache-ll.h\n@@ -269,9 +269,10 @@ void validate_cache_entries(const struct index_state *istate);\n  * the given predicate. This function should only be called if\n  * repo_has_promisor_remote() returns true.\n  */\n-typedef int (*must_prefetch_predicate)(const struct cache_entry *);\n+typedef int (*must_prefetch_predicate)(const struct cache_entry *, void *cb_data);\n void prefetch_cache_entries(const struct index_state *istate,\n-\t\t\t    must_prefetch_predicate must_prefetch);\n+\t\t\t    must_prefetch_predicate must_prefetch,\n+\t\t\t    void *cb_data);\n \n /* Initialize and use the cache information */\n struct lock_file;\ndiff --git a/read-cache.c b/read-cache.c\nindex 8044ff820b..e40f290bb3 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1748,7 +1748,7 @@ static int read_index_extension(struct index_state *istate,\n {\n \tswitch (CACHE_EXT(ext)) {\n \tcase CACHE_EXT_TREE:\n-\t\tistate->cache_tree = cache_tree_read(data, sz);\n+\t\tistate->cache_tree = cache_tree_read(istate->repo, data, sz);\n \t\tbreak;\n \tcase CACHE_EXT_RESOLVE_UNDO:\n \t\tistate->resolve_undo = resolve_undo_read(data, sz, the_hash_algo);\n@@ -3012,7 +3012,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t    !drop_cache_tree && istate->cache_tree) {\n \t\tstrbuf_reset(&sb);\n \n-\t\tcache_tree_write(&sb, istate->cache_tree);\n+\t\tcache_tree_write(istate->repo, &sb, istate->cache_tree);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n \t\tif (err) {\n@@ -3733,7 +3733,8 @@ static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_ta\n }\n \n void prefetch_cache_entries(const struct index_state *istate,\n-\t\t\t    must_prefetch_predicate must_prefetch)\n+\t\t\t    must_prefetch_predicate must_prefetch,\n+\t\t\t    void *cb_data)\n {\n \tint i;\n \tstruct oid_array to_fetch = OID_ARRAY_INIT;\n@@ -3741,7 +3742,7 @@ void prefetch_cache_entries(const struct index_state *istate,\n \tfor (i = 0; i < istate->cache_nr; i++) {\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n-\t\tif (S_ISGITLINK(ce->ce_mode) || !must_prefetch(ce))\n+\t\tif (S_ISGITLINK(ce->ce_mode) || !must_prefetch(ce, cb_data))\n \t\t\tcontinue;\n \t\tif (!odb_read_object_info_extended(the_repository->objects,\n \t\t\t\t\t\t   &ce->oid, NULL,\ndiff --git a/sequencer.c b/sequencer.c\nindex 65afd100d9..97f4119bed 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -814,7 +814,7 @@ static int do_recursive_merge(struct repository *r,\n \n static struct object_id *get_cache_tree_oid(struct index_state *istate)\n {\n-\tif (!cache_tree_fully_valid(istate->cache_tree))\n+\tif (!cache_tree_fully_valid(istate->repo, istate->cache_tree))\n \t\tif (cache_tree_update(istate, 0)) {\n \t\t\terror(_(\"unable to update cache tree\"));\n \t\t\treturn NULL;\ndiff --git a/sparse-index.c b/sparse-index.c\nindex c1fa231a89..102a9dadeb 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -228,7 +228,7 @@ int convert_to_sparse(struct index_state *istate, int flags)\n \tif (index_has_unmerged_entries(istate))\n \t\treturn 0;\n \n-\tif (!cache_tree_fully_valid(istate->cache_tree)) {\n+\tif (!cache_tree_fully_valid(istate->repo, istate->cache_tree)) {\n \t\t/* Clear and recompute the cache-tree */\n \t\tcache_tree_free(&istate->cache_tree);\n \ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 154d6d40a1..20e6d6d824 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -416,7 +416,8 @@ static void report_collided_checkout(struct index_state *index)\n \tstring_list_clear(&list, 0);\n }\n \n-static int must_checkout(const struct cache_entry *ce)\n+static int must_checkout(const struct cache_entry *ce,\n+\t\t\t void *cb_data UNUSED)\n {\n \treturn ce->ce_flags & CE_UPDATE;\n }\n@@ -477,7 +478,7 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t * Prefetch the objects that are to be checked out in the loop\n \t\t * below.\n \t\t */\n-\t\tprefetch_cache_entries(index, must_checkout);\n+\t\tprefetch_cache_entries(index, must_checkout, NULL);\n \n \tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n \n@@ -487,7 +488,7 @@ static int check_updates(struct unpack_trees_options *o,\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n-\t\tif (must_checkout(ce)) {\n+\t\tif (must_checkout(ce, NULL)) {\n \t\t\tsize_t last_pc_queue_size = pc_queue_size();\n \n \t\t\tif (ce->ce_flags & CE_WT_REMOVE)\n@@ -2086,7 +2087,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\t\t}\n \n \t\t\tif (!o->skip_cache_tree_update &&\n-\t\t\t    !cache_tree_fully_valid(o->internal.result.cache_tree))\n+\t\t\t    !cache_tree_fully_valid(the_repository, o->internal.result.cache_tree))\n \t\t\t\tcache_tree_update(&o->internal.result,\n \t\t\t\t\t\t  WRITE_TREE_SILENT |\n \t\t\t\t\t\t  WRITE_TREE_REPAIR);\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551647","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-2-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 02/12] submodule-config: remove uses of `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:01Z","receivedAt":"2026-09-01T11:09:38Z","isPatch":true,"body":"Several functions in the submodule-config subsystem implicitly depend\non `the_repository`. Refactor these to take a `struct repository` as\nparameter and adapt callers accordingly.\n\nNote that as usual with these refactorings, callers simply pass\n`the_repository` even if they already have a different repository\navailable in the calling context. This simplifies the migration and\nensures that we don't have a change in behaviour.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fetch.c             |  2 +-\n builtin/grep.c              |  2 +-\n builtin/submodule--helper.c |  8 ++++----\n submodule-config.c          | 49 ++++++++++++++++++++++++++-------------------\n submodule-config.h          | 12 +++++++----\n submodule.c                 |  2 +-\n t/helper/test-submodule.c   |  4 ++--\n 7 files changed, 45 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ab7db2be06..533fdfe7d8 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -2681,7 +2681,7 @@ int cmd_fetch(int argc,\n \t\tint *rs = config.recurse_submodules == RECURSE_SUBMODULES_DEFAULT\n \t\t\t  ? &config.recurse_submodules : NULL;\n \n-\t\tfetch_config_from_gitmodules(sfjc, rs);\n+\t\tfetch_config_from_gitmodules(the_repository, sfjc, rs);\n \t}\n \n \ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex d3d86abe01..073dfaaf45 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -897,7 +897,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(&real_obj->oid);\n+\t\t\tgitmodules_config_oid(the_repository, &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,\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex e7cd3225fa..aaaa963fd8 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -3041,7 +3041,7 @@ static int module_update(int argc, const char **argv, const char *prefix,\n \t\tNULL\n \t};\n \n-\tupdate_clone_config_from_gitmodules(&opt.max_jobs);\n+\tupdate_clone_config_from_gitmodules(the_repository, &opt.max_jobs);\n \trepo_config(the_repository, git_update_clone_config, &opt.max_jobs);\n \n \targc = parse_options(argc, argv, prefix, module_update_options,\n@@ -3255,7 +3255,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix,\n \t\t    path);\n \n \tconfig_name = xstrfmt(\"submodule.%s.url\", sub->name);\n-\tret = config_set_in_gitmodules_file_gently(config_name, newurl);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, config_name, newurl);\n \n \tif (!ret) {\n \t\trepo_read_gitmodules(the_repository, 0);\n@@ -3311,7 +3311,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix,\n \t\t    path);\n \n \tconfig_name = xstrfmt(\"submodule.%s.branch\", sub->name);\n-\tret = config_set_in_gitmodules_file_gently(config_name, opt_branch);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, config_name, opt_branch);\n \n \tfree(config_name);\n \treturn !!ret;\n@@ -3510,7 +3510,7 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con\n \t\tdie(_(\"please make sure that the .gitmodules file is in the working tree\"));\n \n \tkey = xstrfmt(\"submodule.%s.%s\", name, var);\n-\tret = config_set_in_gitmodules_file_gently(key, value);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, key, value);\n \tfree(key);\n \n \treturn ret;\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f75997402a..f8c2cf7a93 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -667,19 +667,20 @@ static int parse_config(const char *var, const char *value,\n \treturn ret;\n }\n \n-static int gitmodule_oid_from_commit(const struct object_id *treeish_name,\n+static int gitmodule_oid_from_commit(struct repository *repo,\n+\t\t\t\t     const struct object_id *treeish_name,\n \t\t\t\t     struct object_id *gitmodules_oid,\n \t\t\t\t     struct strbuf *rev)\n {\n \tint ret = 0;\n \n \tif (is_null_oid(treeish_name)) {\n-\t\toidclr(gitmodules_oid, the_repository->hash_algo);\n+\t\toidclr(gitmodules_oid, repo->hash_algo);\n \t\treturn 1;\n \t}\n \n \tstrbuf_addf(rev, \"%s:.gitmodules\", oid_to_hex(treeish_name));\n-\tif (repo_get_oid(the_repository, rev->buf, gitmodules_oid) >= 0)\n+\tif (repo_get_oid(repo, rev->buf, gitmodules_oid) >= 0)\n \t\tret = 1;\n \n \treturn ret;\n@@ -689,9 +690,11 @@ static int gitmodule_oid_from_commit(const struct object_id *treeish_name,\n  * (key) with on-demand reading of the appropriate .gitmodules from\n  * revisions.\n  */\n-static const struct submodule *config_from(struct submodule_cache *cache,\n-\t\tconst struct object_id *treeish_name, const char *key,\n-\t\tenum lookup_type lookup_type)\n+static const struct submodule *config_from(struct repository *repo,\n+\t\t\t\t\t   struct submodule_cache *cache,\n+\t\t\t\t\t   const struct object_id *treeish_name,\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   enum lookup_type lookup_type)\n {\n \tstruct strbuf rev = STRBUF_INIT;\n \tsize_t config_size;\n@@ -718,7 +721,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \t\treturn entry->config;\n \t}\n \n-\tif (!gitmodule_oid_from_commit(treeish_name, &oid, &rev))\n+\tif (!gitmodule_oid_from_commit(repo, treeish_name, &oid, &rev))\n \t\tgoto out;\n \n \tswitch (lookup_type) {\n@@ -732,7 +735,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tif (submodule)\n \t\tgoto out;\n \n-\tconfig = odb_read_object(the_repository->objects, &oid,\n+\tconfig = odb_read_object(repo->objects, &oid,\n \t\t\t\t &type, &config_size);\n \tif (!config || type != OBJ_BLOB)\n \t\tgoto out;\n@@ -843,21 +846,22 @@ void repo_read_gitmodules(struct repository *repo, int skip_if_read)\n \trepo->submodule_cache->gitmodules_read = 1;\n }\n \n-void gitmodules_config_oid(const struct object_id *commit_oid)\n+void gitmodules_config_oid(struct repository *repo,\n+\t\t\t   const struct object_id *commit_oid)\n {\n \tstruct strbuf rev = STRBUF_INIT;\n \tstruct object_id oid;\n \n-\tsubmodule_cache_check_init(the_repository);\n+\tsubmodule_cache_check_init(repo);\n \n-\tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n+\tif (gitmodule_oid_from_commit(repo, commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t repo, &oid, repo,\n \t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-\tthe_repository->submodule_cache->gitmodules_read = 1;\n+\trepo->submodule_cache->gitmodules_read = 1;\n }\n \n const struct submodule *submodule_from_name(struct repository *r,\n@@ -865,7 +869,7 @@ const struct submodule *submodule_from_name(struct repository *r,\n \t\tconst char *name)\n {\n \trepo_read_gitmodules(r, 1);\n-\treturn config_from(r->submodule_cache, treeish_name, name, lookup_name);\n+\treturn config_from(r, r->submodule_cache, treeish_name, name, lookup_name);\n }\n \n const struct submodule *submodule_from_path(struct repository *r,\n@@ -873,7 +877,7 @@ const struct submodule *submodule_from_path(struct repository *r,\n \t\tconst char *path)\n {\n \trepo_read_gitmodules(r, 1);\n-\treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n+\treturn config_from(r, r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n /**\n@@ -980,11 +984,12 @@ int print_config_from_gitmodules(struct repository *repo, const char *key)\n \treturn 0;\n }\n \n-int config_set_in_gitmodules_file_gently(const char *key, const char *value)\n+int config_set_in_gitmodules_file_gently(struct repository *repo,\n+\t\t\t\t\t const char *key, const char *value)\n {\n \tint ret;\n \n-\tret = repo_config_set_in_file_gently(the_repository, GITMODULES_FILE, key, NULL, value);\n+\tret = repo_config_set_in_file_gently(repo, GITMODULES_FILE, key, NULL, value);\n \tif (ret < 0)\n \t\t/* Maybe the user already did that, don't error out here */\n \t\twarning(_(\"Could not update .gitmodules entry %s\"), key);\n@@ -1017,13 +1022,15 @@ static int gitmodules_fetch_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n+void fetch_config_from_gitmodules(struct repository *repo,\n+\t\t\t\t  int *max_children,\n+\t\t\t\t  int *recurse_submodules)\n {\n \tstruct fetch_config config = {\n \t\t.max_children = max_children,\n \t\t.recurse_submodules = recurse_submodules\n \t};\n-\tconfig_from_gitmodules(gitmodules_fetch_config, the_repository, &config);\n+\tconfig_from_gitmodules(gitmodules_fetch_config, repo, &config);\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n@@ -1036,7 +1043,7 @@ static int gitmodules_update_clone_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-void update_clone_config_from_gitmodules(int *max_jobs)\n+void update_clone_config_from_gitmodules(struct repository *repo, int *max_jobs)\n {\n-\tconfig_from_gitmodules(gitmodules_update_clone_config, the_repository, max_jobs);\n+\tconfig_from_gitmodules(gitmodules_update_clone_config, repo, max_jobs);\n }\ndiff --git a/submodule-config.h b/submodule-config.h\nindex f55d4e3b61..755570d5d1 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -57,7 +57,8 @@ int option_fetch_parse_recurse_submodules(const struct option *opt,\n int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n int parse_push_recurse_submodules_arg(const char *opt, const char *arg);\n void repo_read_gitmodules(struct repository *repo, int skip_if_read);\n-void gitmodules_config_oid(const struct object_id *commit_oid);\n+void gitmodules_config_oid(struct repository *repo,\n+\t\t\t   const struct object_id *commit_oid);\n \n /**\n  * Same as submodule_from_path but lookup by name.\n@@ -80,7 +81,8 @@ const struct submodule *submodule_from_path(struct repository *r,\n void submodule_free(struct repository *r);\n \n int print_config_from_gitmodules(struct repository *repo, const char *key);\n-int config_set_in_gitmodules_file_gently(const char *key, const char *value);\n+int config_set_in_gitmodules_file_gently(struct repository *repo,\n+\t\t\t\t\t const char *key, const char *value);\n \n /*\n  * Returns 0 if the name is syntactically acceptable as a submodule \"name\"\n@@ -100,8 +102,10 @@ int check_submodule_url(const char *url);\n  * New helpers to retrieve arbitrary configuration from the '.gitmodules' file\n  * should NOT be added.\n  */\n-void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n-void update_clone_config_from_gitmodules(int *max_jobs);\n+void fetch_config_from_gitmodules(struct repository *repo,\n+\t\t\t\t  int *max_children,\n+\t\t\t\t  int *recurse_submodules);\n+void update_clone_config_from_gitmodules(struct repository *repo, int *max_jobs);\n \n /*\n  * Submodule entry that contains relevant information about a\ndiff --git a/submodule.c b/submodule.c\nindex 5c92575888..6fcb606f7e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -133,7 +133,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath)\n \tstrbuf_addstr(&entry, \"submodule.\");\n \tstrbuf_addstr(&entry, submodule->name);\n \tstrbuf_addstr(&entry, \".path\");\n-\tret = config_set_in_gitmodules_file_gently(entry.buf, newpath);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, entry.buf, newpath);\n \tstrbuf_release(&entry);\n \treturn ret;\n }\ndiff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c\nindex 3c5c4c4a09..ea9bef0904 100644\n--- a/t/helper/test-submodule.c\n+++ b/t/helper/test-submodule.c\n@@ -168,7 +168,7 @@ static int cmd__submodule_config_set(int argc, const char **argv)\n \t\tif (!is_writing_gitmodules_ok())\n \t\t\tdie(\"please make sure that the .gitmodules file is in the working tree\");\n \n-\t\treturn config_set_in_gitmodules_file_gently(argv[1], argv[2]);\n+\t\treturn config_set_in_gitmodules_file_gently(the_repository, argv[1], argv[2]);\n \t}\n \tusage_with_options(usage, options);\n }\n@@ -188,7 +188,7 @@ static int cmd__submodule_config_unset(int argc, const char **argv)\n \tif (argc == 2) {\n \t\tif (!is_writing_gitmodules_ok())\n \t\t\tdie(\"please make sure that the .gitmodules file is in the working tree\");\n-\t\treturn config_set_in_gitmodules_file_gently(argv[1], NULL);\n+\t\treturn config_set_in_gitmodules_file_gently(the_repository, argv[1], NULL);\n \t}\n \tusage_with_options(usage, options);\n }\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551648","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-3-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 03/12] submodule-config: stop using `the_hash_algo`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:02Z","receivedAt":"2026-09-01T11:09:41Z","isPatch":true,"body":"We have two uses of `the_hash_algo` in \"submodule-config.c\":\n\n  - One trivial use in `gitmodules_cb`, which we can convert to use the\n    hash algorithm of the repository that's already available in the\n    caller's context.\n\n  - One use where we compute the hashmap key of an object ID. We should\n    only ever get valid, populated object IDs here, and consequently we\n    can easily adapt that function to use the hash algorithm of the\n    passed-in object ID.\n\nAdapt both sites accordingly. Safeguard us against the case where the\npassed-in object ID is _not_ properly initialized. While this case\nshouldn't ever happen, it doesn't hurt to be defensive.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f8c2cf7a93..7c73fa108b 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -133,7 +133,9 @@ void submodule_cache_free(struct submodule_cache *cache)\n static unsigned int hash_oid_string(const struct object_id *oid,\n \t\t\t\t    const char *string)\n {\n-\treturn memhash(oid->hash, the_hash_algo->rawsz) + strhash(string);\n+\tif (oid->algo == GIT_HASH_UNKNOWN)\n+\t\tBUG(\"hashing an object ID with unknown algorithm\");\n+\treturn memhash(oid->hash, hash_algos[oid->algo].rawsz) + strhash(string);\n }\n \n static void cache_put_path(struct submodule_cache *cache,\n@@ -824,7 +826,7 @@ static int gitmodules_cb(const char *var, const char *value,\n \n \tparameter.cache = repo->submodule_cache;\n \tparameter.treeish_name = NULL;\n-\tparameter.gitmodules_oid = null_oid(the_hash_algo);\n+\tparameter.gitmodules_oid = null_oid(repo->hash_algo);\n \tparameter.overwrite = 1;\n \n \treturn parse_config(var, value, ctx, &parameter);\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551649","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-4-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 04/12] submodule-config: stop registering submodule sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:03Z","receivedAt":"2026-09-01T11:09:44Z","isPatch":true,"body":"When reading the \".gitmodules\" file from a blob in a repository other\nthan `the_repository`, we register the repository's object database as\nan in-memory source of `the_repository`'s object database. This call has\nits origins in d9b8b8f896 (submodule-config.c: use repo_get_oid for\nreading .gitmodules, 2019-04-16): back then, `config_with_options()` was\nnot able to read a blob from an arbitrary repository, but would always\nread it via `the_repository`. So even though the blob could be resolved\nin the submodule repository via `repo_get_oid()`, the submodule's object\ndatabase had to be registered as an in-memory source of `the_repository`\nso that the subsequent object read was able to find the blob at all.\n\nThat need went away with e3e8bf046e (submodule-config: pass repo\nupon blob config read, 2021-08-16), which taught the config machinery\nto read the blob from the repository we pass to it. The same series\nconverted the eager submodule source registration into a lazy mechanism\nthat only registers submodule sources with the object database when an\nobject lookup failed. The intent though was that we don't ever have to\nfall back to this mechanism in the first place, and to verify that this\nis the case we introduced GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB. If set,\nthen any such lazy registration would cause us to BUG.\n\nAt the beginning of this series, we still triggered this bug in t1092.\nBut now that we have converted the \"cache-tree\" subsystem to not depend\non `the_repository` anymore it also knows to properly access objects via\nthe submodule. With that change, GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\ndoes not cause any failures anymore.\n\nRemove the call to `odb_add_submodule_source_by_path()`. This removes\nthe last user of `the_repository`, so at the same time we can also get\nrid of `USE_THE_REPOSITORY_VARIABLE`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 4 ----\n 1 file changed, 4 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 7c73fa108b..37c3be377b 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\n@@ -803,9 +802,6 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void\n \t\t} else if (repo_get_oid(repo, GITMODULES_INDEX, &oid) >= 0 ||\n \t\t\t   repo_get_oid(repo, GITMODULES_HEAD, &oid) >= 0) {\n \t\t\tconfig_source.blob = oidstr = xstrdup(oid_to_hex(&oid));\n-\t\t\tif (repo != the_repository)\n-\t\t\t\todb_add_submodule_source_by_path(the_repository->objects,\n-\t\t\t\t\t\t\t\t repo->objects->sources->path);\n \t\t} else {\n \t\t\tgoto out;\n \t\t}\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551650","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-5-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 05/12] builtin/grep: stop registering submodule ODB as source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:04Z","receivedAt":"2026-09-01T11:09:47Z","isPatch":true,"body":"Same as with the preceding commit, git-grep(1) registers each\nsubmodule's object database as an in-memory source of the main object\ndatabase before grepping it. This was introduced as an eager alternate\nregistration and converted into the lazy mechanism via 8d33c3af0b (grep:\nuse submodule-ODB-as-alternate lazy-addition, 2021-08-16).\n\nStarting with 0693806bf8 (grep: add repository to OID grep sources,\n2021-08-16), the command instead knows to pass submodule repositories to\nour workers, which means that those now use that repository to look up\nobjects, too. As a consequence, registering submodule sources as\nalternates is not required anymore.\n\nRemove the logic to register submodule sources. Unfortunately, this does\nnot allow us to get rid of the object read lock as initializing the\nsubrepository is still racy.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c | 26 ++++++--------------------\n 1 file changed, 6 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 073dfaaf45..b045f8a488 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -463,16 +463,6 @@ static int grep_submodule(struct grep_opt *opt,\n \tALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc);\n \trepos_to_free[repos_to_free_nr++] = subrepo;\n \n-\t/*\n-\t * NEEDSWORK: repo_read_gitmodules() might call\n-\t * odb_add_to_alternates_memory() via config_from_gitmodules(). This\n-\t * operation causes a race condition with concurrent object readings\n-\t * performed by the worker threads. That's why we need obj_read_lock()\n-\t * here. It should be removed once it's no longer necessary to add the\n-\t * subrepo's odbs to the in-memory alternates list.\n-\t */\n-\tobj_read_lock();\n-\n \t/*\n \t * NEEDSWORK: when reading a submodule, the sparsity settings in the\n \t * superproject are incorrectly forgotten or misused. For example:\n@@ -498,18 +488,14 @@ static int grep_submodule(struct grep_opt *opt,\n \t *\tditto.\n \t *\n \t * Note that this list is not exhaustive.\n+\t *\n+\t * NEEDSWORK: initializing the subrepository is not thread-safe,\n+\t * either, as it may cause us to race around `get_main_ref_store()`. We\n+\t * thus need to hold the object-read lock to serialize all readers with\n+\t * one another.\n \t */\n+\tobj_read_lock();\n \trepo_read_gitmodules(subrepo, 0);\n-\n-\t/*\n-\t * All code paths tested by test code no longer need submodule ODBs to\n-\t * be added as alternates, but add it to the list just in case.\n-\t * Submodule ODBs added through add_submodule_odb_by_path() will be\n-\t * lazily registered as alternates when needed (and except in an\n-\t * unexpected code interaction, it won't be needed).\n-\t */\n-\todb_add_submodule_source_by_path(the_repository->objects,\n-\t\t\t\t\t subrepo->objects->sources->path);\n \tobj_read_unlock();\n \n \tmemcpy(&subopt, opt, sizeof(subopt));\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551651","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-6-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 06/12] odb: remove infrastructure to register submodule sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:05Z","receivedAt":"2026-09-01T11:09:49Z","isPatch":true,"body":"The preceding commits have removed the last two users of\n`odb_add_submodule_source_by_path()`. The mechanism was only ever\nmeant as a transitional crutch while migrating submodule object\naccess away from \"add the submodule ODB as an alternate of\nthe_repository\" towards explicitly passing the submodule repository,\nsee a35e03dee0 (submodule: lazily add submodule ODBs as alternates,\n2021-08-16). Remove it.\n\nAs GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB is now a no-op, remove its\ndocumentation and the exports from the test suite, as well.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                                  | 36 ----------------------------------\n odb.h                                  | 14 -------------\n t/README                               |  7 -------\n t/t5526-fetch-submodules.sh            |  3 ---\n t/t5531-deep-submodule-push.sh         |  3 ---\n t/t5545-push-options.sh                |  3 ---\n t/t5572-pull-submodule.sh              |  3 ---\n t/t6437-submodule-merge.sh             |  3 ---\n t/t7418-submodule-sparse-gitmodules.sh |  3 ---\n t/t7814-grep-recurse-submodules.sh     |  3 ---\n 10 files changed, 78 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 6d5943e5ea..2f8a70a90c 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -388,12 +388,6 @@ struct odb_source *odb_find_source_or_die(struct object_database *odb, const cha\n \treturn source;\n }\n \n-void odb_add_submodule_source_by_path(struct object_database *odb,\n-\t\t\t\t      const char *path)\n-{\n-\tstring_list_insert(&odb->submodule_source_paths, path);\n-}\n-\n static void fill_alternate_refs_command(struct repository *repo,\n \t\t\t\t\tstruct child_process *cmd,\n \t\t\t\t\tconst char *repo_path)\n@@ -549,23 +543,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-static int register_all_submodule_sources(struct object_database *odb)\n-{\n-\tint ret = odb->submodule_source_paths.nr;\n-\n-\tfor (size_t i = 0; i < odb->submodule_source_paths.nr; i++)\n-\t\todb_add_to_alternates_memory(odb,\n-\t\t\t\t\t     odb->submodule_source_paths.items[i].string);\n-\tif (ret) {\n-\t\tstring_list_clear(&odb->submodule_source_paths, 0);\n-\t\ttrace2_data_intmax(\"submodule\", odb->repo,\n-\t\t\t\t   \"register_all_submodule_sources/registered\", ret);\n-\t\tif (git_env_bool(\"GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\", 0))\n-\t\t\tBUG(\"register_all_submodule_sources() called\");\n-\t}\n-\treturn ret;\n-}\n-\n static enum odb_read_status do_oid_object_info_extended(struct object_database *odb,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n \t\t\t\t\t\t\tstruct object_info *oi, unsigned flags)\n@@ -614,16 +591,6 @@ static enum odb_read_status do_oid_object_info_extended(struct object_database *\n \t\t\t}\n \t\t}\n \n-\t\t/*\n-\t\t * This might be an attempt at accessing a submodule object as\n-\t\t * if it were in main object store (having called\n-\t\t * `odb_add_submodule_source_by_path()` on that submodule's\n-\t\t * ODB). If any such ODBs exist, register them and try again.\n-\t\t */\n-\t\tif (register_all_submodule_sources(odb))\n-\t\t\t/* We added some alternates; retry */\n-\t\t\tcontinue;\n-\n \t\t/* Check if it is a missing object */\n \t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n@@ -1109,7 +1076,6 @@ struct object_database *odb_new(struct repository *repo,\n \tCALLOC_ARRAY(o, 1);\n \to->repo = repo;\n \tpthread_mutex_init(&o->replace_mutex, NULL);\n-\tstring_list_init_dup(&o->submodule_source_paths);\n \thashmap_init(&o->source_by_path, odb_source_by_path_cmp, o, 0);\n \to->source_paths_icase = -1;\n \n@@ -1166,8 +1132,6 @@ void odb_free(struct object_database *o)\n \todb_close(o);\n \todb_free_sources(o);\n \n-\tstring_list_clear(&o->submodule_source_paths, 0);\n-\n \tfree(o);\n }\n \ndiff --git a/odb.h b/odb.h\nindex 248ee9cdfa..54548efc55 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -89,12 +89,6 @@ struct object_database {\n \tunsigned long object_count;\n \tunsigned object_count_flags;\n \tunsigned object_count_valid : 1;\n-\n-\t/*\n-\t * Submodule source paths that will be added as additional sources to\n-\t * allow lookup of submodule objects via the main object database.\n-\t */\n-\tstruct string_list submodule_source_paths;\n };\n \n enum odb_new_flags {\n@@ -224,14 +218,6 @@ void odb_restore_primary_source(struct object_database *odb,\n \t\t\t\tstruct odb_source *restore_source,\n \t\t\t\tconst char *old_path);\n \n-/*\n- * Call odb_add_submodule_source_by_path() to add the submodule at the given\n- * path to a list. The object stores of all submodules in that list will be\n- * added as additional sources in the object store when looking up objects.\n- */\n-void odb_add_submodule_source_by_path(struct object_database *odb,\n-\t\t\t\t      const char *path);\n-\n /*\n  * Iterate through all alternates of the database and execute the provided\n  * callback function for each of them. Stop iterating once the callback\ndiff --git a/t/README b/t/README\nindex 9a9daaf2af..f831c5355b 100644\n--- a/t/README\n+++ b/t/README\n@@ -462,13 +462,6 @@ GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting\n to <n> and 'checkout.thresholdForParallelism' to 0, forcing the\n execution of the parallel-checkout code.\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=<boolean>, when true, makes\n-registering submodule ODBs as alternates a fatal action. Support for\n-this environment variable can be removed once the migration to\n-explicitly providing repositories when accessing submodule objects is\n-complete or needs to be abandoned for whatever reason (in which case the\n-migrated codepaths still retain their performance benefits).\n-\n GIT_TEST_REQUIRE_PREREQ=<list> allows specifying a space separated list of\n prereqs that are required to succeed. If a prereq in this list is triggered by\n a test and then fails then the whole test run will abort. This can help to make\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 7b3b7359da..37d7373b36 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -3,9 +3,6 @@\n \n test_description='Recursive \"git fetch\" for submodules'\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n pwd=$(pwd)\ndiff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh\nindex 7d239dd31f..73429ec6e3 100755\n--- a/t/t5531-deep-submodule-push.sh\n+++ b/t/t5531-deep-submodule-push.sh\n@@ -5,9 +5,6 @@ test_description='test push with submodules'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh\nindex fb13549da7..239edd7d62 100755\n--- a/t/t5545-push-options.sh\n+++ b/t/t5545-push-options.sh\n@@ -5,9 +5,6 @@ test_description='pushing to a repository using push options'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n mk_repo_pair () {\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 42d14328b6..9969a3294e 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -2,9 +2,6 @@\n \n test_description='pull can handle submodules'\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-submodule-update.sh\n \ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 107e13afbc..1546d5f773 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -5,9 +5,6 @@ test_description='merging with submodules'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n #\ndiff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh\nindex dde11ecce8..cf94e30e78 100755\n--- a/t/t7418-submodule-sparse-gitmodules.sh\n+++ b/t/t7418-submodule-sparse-gitmodules.sh\n@@ -12,9 +12,6 @@ The test setup uses a sparse checkout, however the same scenario can be set up\n also by committing .gitmodules and then just removing it from the filesystem.\n '\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh\nindex e1cf53dc9e..3d149d34c1 100755\n--- a/t/t7814-grep-recurse-submodules.sh\n+++ b/t/t7814-grep-recurse-submodules.sh\n@@ -9,9 +9,6 @@ submodules.\n TEST_CREATE_REPO_NO_TEMPLATE=1\n . ./test-lib.sh\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n test_expect_success 'setup directory structure and submodule' '\n \techo \"(1|2)d(3|4)\" >a &&\n \tmkdir b &&\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551652","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-7-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 07/12] tmp-objdir: drop unused function to register alternate","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:06Z","receivedAt":"2026-09-01T11:09:51Z","isPatch":true,"body":"The last caller of `tmp_objdir_add_as_alternate()` went away in\nbdee7b3013 (builtin/receive-pack: stage incoming objects via ODB\ntransactions, 2026-07-10) and is unused now. Remove the function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n tmp-objdir.c | 5 -----\n tmp-objdir.h | 6 ------\n 2 files changed, 11 deletions(-)\n\ndiff --git a/tmp-objdir.c b/tmp-objdir.c\nindex 0eaa79ffd7..deaaf6ba2e 100644\n--- a/tmp-objdir.c\n+++ b/tmp-objdir.c\n@@ -321,11 +321,6 @@ const char **tmp_objdir_env(const struct tmp_objdir *t)\n \treturn t->env.v;\n }\n \n-void tmp_objdir_add_as_alternate(const struct tmp_objdir *t)\n-{\n-\todb_add_to_alternates_memory(t->repo->objects, t->path.buf);\n-}\n-\n struct odb_source *tmp_objdir_replace_primary_odb(struct tmp_objdir *t,\n \t\t\t\t\t\t  int will_destroy)\n {\ndiff --git a/tmp-objdir.h b/tmp-objdir.h\nindex 81eb927413..05f0d08d10 100644\n--- a/tmp-objdir.h\n+++ b/tmp-objdir.h\n@@ -55,12 +55,6 @@ int tmp_objdir_destroy(struct tmp_objdir *);\n  */\n void tmp_objdir_discard_objects(struct tmp_objdir *);\n \n-/*\n- * Add the temporary object directory as an alternate object store in the\n- * current process.\n- */\n-void tmp_objdir_add_as_alternate(const struct tmp_objdir *);\n-\n /*\n  * Replaces the writable object store in the current process with the temporary\n  * object directory and makes the former main object store an alternate.\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551653","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-8-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 08/12] odb/packed: fix memory leaks when freeing source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:07Z","receivedAt":"2026-09-01T11:09:54Z","isPatch":true,"body":"When freeing a \"packed\" source we don't close either its packs nor its\nmulti-pack indices. This can cause memory leaks in case we create an\nad-hoc packed source. As we used to always link packed sources to the\nmain object database we never noticed this issue until now, but it's\ngoing to surface in subsequent commits where we stop linking them.\n\nPlug the memory leaks by closing the source first.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-packed.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 1d90e714e6..166e76e2d6 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -844,6 +844,7 @@ static void odb_source_packed_free(struct odb_source *source)\n \n \tchdir_notify_unregister(odb_source_packed_reparent, packed);\n \n+\todb_source_close(source);\n \tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)\n \t\tfree(e->pack);\n \tpackfile_list_clear(&packed->packs);\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551654","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-9-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 09/12] builtin/multi-pack-index: refuse unknown sources with \"--object-dir=\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:08Z","receivedAt":"2026-09-01T11:09:56Z","isPatch":true,"body":"Users can tell git-multi-pack-index(1) to access multi-pack indices that\nare stored in a different object directory via the \"--object-dir=\"\noption. This allows them to for example write or verify a multi-pack\nindex other than the one located in the main object directory in case a\nrepository has alternates with multiple multi-pack indices.\n\nBut while the documentation explicitly points out that the specified\nobject directory must be an alternate of the current repository, we\nnever verify that property. Instead, starting with 017db7bb14 (midx:\nload multi-pack indices via their source, 2025-08-11), we now construct\nan ad-hoc source and link it to the main object directory.\n\nBesides contradicting the documentation, it's dubious that this really\nought to work in the first place: creating a multi-pack index (and\npotentially a bitmap) for a completely foreign object directory is of\nquestionable value, as bitmap commit selection operates on the invoking\nrepository's refs. Furthermore, this is the only remaining caller\noutside of our test helpers that constructs an ad-hoc source and links\nit to the database, and we want to get rid of this mechanism as part of\nthis series.\n\nStop constructing the ad-hoc source and instead refuse the operation.\nWhile this results in a change in behaviour, this restriction has been\ndocumented as such ever since f57a739691 (midx: avoid opening multiple\nMIDXs when writing, 2021-09-01).\n\nNote that this change requires us to adapt one test chain in t5319, as\nit creates an object directory that is not connected to any repository\nand then uses it via \"--object-dir=\". The setup itself already documents\nthis and does the necessary gymnastics to link the object directory to a\ntemporary repository, but subsequent tests don't. Adapt those tests to\nretain and reuse the temporary repository.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/multi-pack-index.c  | 3 ++-\n t/t5319-multi-pack-index.sh | 9 ++++-----\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 6e73c85cde..753bd53a70 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -90,7 +90,8 @@ static struct odb_source_files *handle_object_dir_option(struct repository *repo\n {\n \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n \tif (!source)\n-\t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n+\t\tdie(_(\"object directory is not an alternate of the current repository: '%s'\"),\n+\t\t    opts.object_dir);\n \treturn odb_source_files_downcast(source);\n }\n \ndiff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh\nindex 68143cb5b7..00e90f163f 100755\n--- a/t/t5319-multi-pack-index.sh\n+++ b/t/t5319-multi-pack-index.sh\n@@ -698,10 +698,9 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '\n \tcorrupt_data $idx64 $(test_oid idxoff) \"\\02\" &&\n \t# objects64 is not a real repository, but can serve as an alternate\n \t# anyway so we can write a MIDX into it\n-\tgit init repo &&\n-\ttest_when_finished \"rm -fr repo\" &&\n+\tgit init repo64 &&\n \t(\n-\t\tcd repo &&\n+\t\tcd repo64 &&\n \t\t( cd ../objects64 && pwd ) >.git/objects/info/alternates &&\n \t\tmidx64=$(git multi-pack-index --object-dir=../objects64 write)\n \t) &&\n@@ -709,7 +708,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '\n '\n \n test_expect_success 'verify multi-pack-index with 64-bit offsets' '\n-\tgit multi-pack-index verify --object-dir=objects64\n+\tgit -C repo64 multi-pack-index verify --object-dir=../objects64\n '\n \n NUM_OBJECTS=63\n@@ -721,7 +720,7 @@ MIDX_BYTE_LARGE_OFFSET=$(($MIDX_OFFSET_LARGE_OFFSETS + 3))\n \n test_expect_success 'verify incorrect 64-bit offset' '\n \tcorrupt_midx_and_verify $MIDX_BYTE_LARGE_OFFSET \"\\07\" objects64 \\\n-\t\t\"incorrect object offset\"\n+\t\t\"incorrect object offset\" \"git -C repo64 multi-pack-index verify --object-dir=../objects64\"\n '\n \n test_expect_success 'setup expire tests' '\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551655","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-10-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 10/12] t/helper: adapt read-midx to not link ad-hoc source anymore","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:09Z","receivedAt":"2026-09-01T11:09:59Z","isPatch":true,"body":"Same as in the preceding commit, refactor the setup of ad-hoc object\ndatabase sources when accessing a multi-pack index in an arbitrary\nlocation to not link the newly created source into the main object\ndatabase anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/helper/test-read-midx.c | 43 +++++++++++++++++++++++++++++++------------\n 1 file changed, 31 insertions(+), 12 deletions(-)\n\ndiff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c\nindex 27a05da957..1f7a1927e4 100644\n--- a/t/helper/test-read-midx.c\n+++ b/t/helper/test-read-midx.c\n@@ -5,34 +5,42 @@\n #include \"midx.h\"\n #include \"repository.h\"\n #include \"odb.h\"\n+#include \"odb/source-packed.h\"\n #include \"pack-bitmap.h\"\n #include \"packfile.h\"\n #include \"setup.h\"\n #include \"gettext.h\"\n #include \"pack-revindex.h\"\n \n-static struct multi_pack_index *setup_midx(const char *object_dir)\n+static struct multi_pack_index *setup_midx(const char *object_dir,\n+\t\t\t\t\t   struct odb_source_packed **out)\n {\n-\tstruct odb_source_files *files;\n+\tstruct odb_source_packed *packed;\n \tstruct odb_source *source;\n+\n \tsetup_git_directory(the_repository);\n+\n \tsource = odb_find_source(the_repository->objects, object_dir);\n-\tif (!source)\n-\t\tsource = odb_add_to_alternates_memory(the_repository->objects,\n-\t\t\t\t\t\t      object_dir);\n-\tfiles = odb_source_files_downcast(source);\n+\tif (source) {\n+\t\tpacked = odb_source_files_downcast(source)->packed;\n+\t} else {\n+\t\tpacked = odb_source_packed_new(the_repository->objects,\n+\t\t\t\t\t       object_dir, false);\n+\t\t*out = packed;\n+\t}\n \n-\treturn load_multi_pack_index(files->packed);\n+\treturn load_multi_pack_index(packed);\n }\n \n static int read_midx_file(const char *object_dir, const char *checksum,\n \t\t\t  int show_objects)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tuint32_t i;\n \tstruct multi_pack_index *m, *tip;\n \tint ret = 0;\n \n-\tm = tip = setup_midx(object_dir);\n+\tm = tip = setup_midx(object_dir, &packed);\n \n \tif (!m)\n \t\treturn 1;\n@@ -91,29 +99,35 @@ static int read_midx_file(const char *object_dir, const char *checksum,\n \n out:\n \tclose_midx(tip);\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \n \treturn ret;\n }\n \n static int read_midx_checksum(const char *object_dir)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tstruct multi_pack_index *m;\n \n-\tm = setup_midx(object_dir);\n+\tm = setup_midx(object_dir, &packed);\n \tif (!m)\n \t\treturn 1;\n \tprintf(\"%s\\n\", midx_get_checksum_hex(m));\n \n \tclose_midx(m);\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \treturn 0;\n }\n \n static int read_midx_preferred_pack(const char *object_dir)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tstruct multi_pack_index *midx = NULL;\n \tuint32_t preferred_pack;\n \n-\tmidx = setup_midx(object_dir);\n+\tmidx = setup_midx(object_dir, &packed);\n \tif (!midx)\n \t\treturn 1;\n \n@@ -124,17 +138,21 @@ static int read_midx_preferred_pack(const char *object_dir)\n \t}\n \n \tprintf(\"%s\\n\", midx->pack_names[preferred_pack]);\n+\n \tclose_midx(midx);\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \treturn 0;\n }\n \n static int read_midx_bitmapped_packs(const char *object_dir)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tstruct multi_pack_index *midx = NULL;\n \tstruct bitmapped_pack pack;\n \tuint32_t i;\n \n-\tmidx = setup_midx(object_dir);\n+\tmidx = setup_midx(object_dir, &packed);\n \tif (!midx)\n \t\treturn 1;\n \n@@ -150,7 +168,8 @@ static int read_midx_bitmapped_packs(const char *object_dir)\n \t}\n \n \tclose_midx(midx);\n-\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \treturn 0;\n }\n \n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551656","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-11-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 11/12] t/helper: stop registering alternates in \"ref-store\" command","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:10Z","receivedAt":"2026-09-01T11:10:02Z","isPatch":true,"body":"When using the \"ref-store\" command we support access to multiple\ndifferent reference stores. As part of that we allow the caller to\nexplicitly exercise stores of a submodule. This allows us to verify\nlow-level behaviour of submodule stores, which is exercised in t1406.\n\nWhen doing so we also link the submodule's object database into the main\nobject database. The intent of this is that it allows us to access\nobjects of the submodule, too. But that functionality is not even\nneeded anymore: when creating a submodule reference store, we will first\ninitialize the submodule repository and then initialize the store with\nthat repository. And as the reference subsystem doesn't depend on\n`the_repository` anymore all subsequent object lookups performed by the\nreference store will be routed to the submodule repository.\n\nIt is thus not needed anymore to register the submodule object store\nwith the main object database. Remove the call.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/helper/test-ref-store.c | 8 --------\n 1 file changed, 8 deletions(-)\n\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 5a9a3053d9..db58f00589 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -74,14 +74,6 @@ static const char **get_store(const char **argv, struct ref_store **refs)\n \t} else if (!strcmp(argv[0], \"main\")) {\n \t\t*refs = get_main_ref_store(the_repository);\n \t} else if (skip_prefix(argv[0], \"submodule:\", &gitdir)) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\n-\t\tif (!repo_submodule_path_append(the_repository,\n-\t\t\t\t\t\t&sb, gitdir, \"objects/\"))\n-\t\t\tdie(\"computing submodule path failed\");\n-\t\todb_add_to_alternates_memory(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n-\n \t\t*refs = repo_get_submodule_ref_store(the_repository, gitdir);\n \t} else if (skip_prefix(argv[0], \"worktree:\", &gitdir)) {\n \t\tstruct worktree **p, **worktrees = get_worktrees(the_repository);\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551657","messageId":"20260901-pks-odb-registering-in-memory-sources-v1-12-97a312d5fa25@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH 12/12] odb: remove the ability to link sources ad-hoc","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-01T11:09:11Z","receivedAt":"2026-09-01T11:10:04Z","isPatch":true,"body":"Over the course of this patch series we have adapted all callers of\n`odb_add_to_alternates_memory()` to not do so anymore. Remove the\nfunction.\n\nThis series of refactorings doesn't only simplify our code base. More\nimportantly, with those changes in place we can now unconditionally\nassume that the list of sources linked to the object database only\nconsists of the primary source and its alternates. This serves as the\nfoundation to eventually move handling of alternates into the \"files\"\nbackend itself.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c | 6 ------\n odb.h | 8 --------\n 2 files changed, 14 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 2f8a70a90c..5fe081496f 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -247,12 +247,6 @@ void odb_add_to_alternates_file(struct object_database *odb,\n \todb_add_alternate_recursively(odb, dir, 0);\n }\n \n-struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n-\t\t\t\t\t\tconst char *dir)\n-{\n-\treturn odb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,\n \t\t\t\t\t\t    const char *dir, int will_destroy,\n \t\t\t\t\t\t    struct odb_source **prev_source)\ndiff --git a/odb.h b/odb.h\nindex 54548efc55..9025239df5 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -258,14 +258,6 @@ int odb_has_alternates(struct object_database *odb);\n void odb_add_to_alternates_file(struct object_database *odb,\n \t\t\t\tconst char *dir);\n \n-/*\n- * Add the directory to the in-memory list of alternate sources (along with any\n- * recursive alternates it points to), but do not modify the on-disk alternates\n- * file.\n- */\n-struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n-\t\t\t\t\t\tconst char *dir);\n-\n /*\n  * Read an object from the database. Returns the object data and assigns object\n  * type and size to the `type` and `size` pointers, if these pointers are\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551704","messageId":"xmqqzey0ljcv.fsf@gitster.g","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-1-97a312d5fa25@pks.im","subject":"Re: [PATCH 01/12] cache-tree: remove dependency on `the_repository`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-01T22:03:44Z","receivedAt":"2026-09-01T22:03:47Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The \"cache-tree\" subsystem still depends on `the_repository`. Adapt it\n> to instead use repositories provided via the context, either as a new\n> parameter or the one passed in via `struct index_state`.\n>\n> Besides getting rid of `the_repository`, this also removes the last\n> dependency on registering submodule sources with the main object\n> database. When reading gitmodules from a submodule's index we implicitly\n> read that object via `the_repository`'s object database, which is of\n> course wrong. This works though because we would then register the\n> submodule's object database with the main object database, but a later\n> patch is going to get rid of that mechanism.\n\nAs \"the_repository, \" is quite a long string, many lines bust our\nusual ~70 column limit, but other than that the changes look quite\nstraight-forward.\n\nWe probably should make the cache_tree_fully_valid() into file scope\nstatic.  Your new cache_tree_fully_valid() that takes the repository\nand a cache_tree, which will be file-scope static, will gain a new\nthin wrapper around it, i.e.,\n\n    int has_valid_cache_tree(struct index_state *istate)\n    {\n\treturn cache_tree_fully_valid(istate->repo, istate->cache_tree);\n    }\n\nMigrate the callers outside cache_tree.c file to use it; outside the\nimplementation, nobody has any business talking about a subdirectory\nof any cache-tree, or any cache-tree outside the context of the istate\nit belongs to.\n\nOther than that, looking good.\n"},{"id":"551712","messageId":"xmqqv78oliah.fsf@gitster.g","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-6-97a312d5fa25@pks.im","subject":"Re: [PATCH 06/12] odb: remove infrastructure to register submodule sources","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-01T22:26:46Z","receivedAt":"2026-09-01T22:26:53Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The preceding commits have removed the last two users of\n> `odb_add_submodule_source_by_path()`. The mechanism was only ever\n> meant as a transitional crutch while migrating submodule object\n> access away from \"add the submodule ODB as an alternate of\n> the_repository\" towards explicitly passing the submodule repository,\n> see a35e03dee0 (submodule: lazily add submodule ODBs as alternates,\n> 2021-08-16). Remove it.\n\nYay.  Very nice.\n"},{"id":"551713","messageId":"xmqqmru0lhcn.fsf@gitster.g","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-1-97a312d5fa25@pks.im","subject":"Re: [PATCH 01/12] cache-tree: remove dependency on `the_repository`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-01T22:47:04Z","receivedAt":"2026-09-01T22:47:06Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> diff --git a/cache-tree.c b/cache-tree.c\n> index a220372a42..6e4d82f3e8 100644\n> --- a/cache-tree.c\n> +++ b/cache-tree.c\n> ...\n> @@ -775,7 +781,7 @@ struct tree *write_in_core_index_as_tree(struct repository *repo,\n>  \tint was_valid, ret;\n>  \n>  \twas_valid = index_state->cache_tree &&\n> -\t\t    cache_tree_fully_valid(index_state->cache_tree);\n> +\t\t    cache_tree_fully_valid(repo, index_state->cache_tree);\n\nHere you use \"repo\".  I do not know offhand if the two callers of\nthis function write_in_core_index_as_tree() are correct (they are in\nbuiltin/history.c and not my code), but if they are, then \"repo\"\nshould match \"index_state->repo\".  And if they aren't, this code is\nbroken.\n\nEither way, passing index_state->repo would be the right thing to do\nhere.  Perhaps write_in_core_index_as_tree() should be corrected to\ndrop its \"repo\" parameter.\n\n> diff --git a/unpack-trees.c b/unpack-trees.c\n> index 154d6d40a1..20e6d6d824 100644\n> --- a/unpack-trees.c\n> +++ b/unpack-trees.c\n> ...\n> @@ -2086,7 +2087,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n>  \t\t\t}\n>  \n>  \t\t\tif (!o->skip_cache_tree_update &&\n> -\t\t\t    !cache_tree_fully_valid(o->internal.result.cache_tree))\n> +\t\t\t    !cache_tree_fully_valid(the_repository, o->internal.result.cache_tree))\n\nThe o->internal.result is a full fledged istate instance and comes\nwith its own repository.  It may or may not be the same as\nthe_repository.\n\nIf this were a misconversion, it would argue strongly for a thin\nwrapper I suggested in the other message, that only takes an istate\ninstance and infers which repository and which cache-tree to work\nwith.\n"},{"id":"551739","messageId":"apf5HjD7IHwStiS4@pks.im","threadId":"66246","inReplyTo":"xmqqzey0ljcv.fsf@gitster.g","subject":"Re: [PATCH 01/12] cache-tree: remove dependency on `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-02T10:23:26Z","receivedAt":"2026-09-02T10:23:39Z","isPatch":true,"body":"On Tue, Sep 01, 2026 at 03:03:44PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The \"cache-tree\" subsystem still depends on `the_repository`. Adapt it\n> > to instead use repositories provided via the context, either as a new\n> > parameter or the one passed in via `struct index_state`.\n> >\n> > Besides getting rid of `the_repository`, this also removes the last\n> > dependency on registering submodule sources with the main object\n> > database. When reading gitmodules from a submodule's index we implicitly\n> > read that object via `the_repository`'s object database, which is of\n> > course wrong. This works though because we would then register the\n> > submodule's object database with the main object database, but a later\n> > patch is going to get rid of that mechanism.\n> \n> As \"the_repository, \" is quite a long string, many lines bust our\n> usual ~70 column limit, but other than that the changes look quite\n> straight-forward.\n> \n> We probably should make the cache_tree_fully_valid() into file scope\n> static.  Your new cache_tree_fully_valid() that takes the repository\n> and a cache_tree, which will be file-scope static, will gain a new\n> thin wrapper around it, i.e.,\n> \n>     int has_valid_cache_tree(struct index_state *istate)\n>     {\n> \treturn cache_tree_fully_valid(istate->repo, istate->cache_tree);\n>     }\n> \n> Migrate the callers outside cache_tree.c file to use it; outside the\n> implementation, nobody has any business talking about a subdirectory\n> of any cache-tree, or any cache-tree outside the context of the istate\n> it belongs to.\n\nYeah, I think that's a good idea indeed. But instead of introducing a\nnew `has_valid_cache_tree()`, I think it makes more sense to just adapt\nthe current function and introduce `cache_tree_fully_valid_recursive()`\nas internal function instead. That also matches the other existing\ninterfaces like `cache_tree_fully_valid()` better.\n\nThanks!\n\nPatrick\n"},{"id":"552501","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im","subject":"[PATCH v3 00/13] odb: stop registering in-memory sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:43Z","receivedAt":"2026-09-11T05:51:50Z","isPatch":true,"body":"Hi,\n\nthe object database has a list of sources that is used for two\ndifferent purposes:\n\n  - We use it to track the list of alternates.\n\n  - We use it to track temporary in-memory sources that we create for\n    various purposes. Most importantly, this is used to link object\n    database sources from submodules into the main store.\n\nThis dual-use is quite awkward, as it mixes two different levels of\nconcerns and thus as a consequence makes both harder to reason about.\nIt's also a source of bugs: we make assumptions about the ordering of\nsources all over the place, and we furthermore assume in other places\nthat the sources only contain alternates in the first place. I don't\nthink this surfaces in the form of real bugs, but I've long disliked\nthis dual-use.\n\nFurthermore, we want to migrate handling of alternates into the \"files\"\nbackend itself in a subsequent patch series. This is most importantly to\nfix a performance regression by making the backend own all of its\nalternates, but it also fixes a couple of longer-standing design issues\nthat I've been struggling with [1].\n\nMost importantly though: this whole machinery is not even needed at all.\nA couple years ago we have already refactored our codebase so that\nsubmodule sources don't even have to be linked into the main object\ndatabase anymore. And all the other use cases where we link sources into\nthe main object database can be trivially converted, too.\n\nSo this patch series does exactly that: it removes the mechanism to link\nad-hoc sources into the object database entirely. This ensures that the\nlist of sources is exactly the list of alternates, and that makes it\neasier to move them into the \"files\" backend in a subsequent patch\nseries.\n\nThere is one exception though: creating transactions still creates a\ntemporary quarantine directory. This mechanism is left as-is for now,\nbut as it's an implementation detail of the \"files\" backend anyway\nthat's not conflicting with our above stated goals.\n\nThis series is built on top of 1630431f32 (The 21st batch, 2026-08-31)\nwith ty/repository-fetch-if-missing at 508ec9837c (repository: move\nfetch_if_missing into struct repository, 2026-08-15) merged into it.\nThere's still two merge conflicts, but these are trivial to resolve: in\n\"odb.c\" and \"odb.h\" you simply remove both ours and theirs, and in\n\"builtin/multi-pack-index.c\" you only need to munge the parameters a\nbit.\n\nChanges in v3:\n  - Improve commit message clarity a tiny bit :)\n  - Link to v2: https://patch.msgid.link/20260902-pks-odb-registering-in-memory-sources-v2-0-c6ca12fdea4d@pks.im\n\nChanges in v2:\n  - Adapt `cache_tree_fully_valid()` to take a `struct index_state` as\n    input instead of taking both a repository and a cache tree, as\n    suggested by Junio.\n  - Link to v1: https://patch.msgid.link/20260901-pks-odb-registering-in-memory-sources-v1-0-97a312d5fa25@pks.im\n\nThanks!\n\nPatrick\n\n[1]: <amLgMqkqxR8mKIbT@pks.im>\n\n---\nPatrick Steinhardt (13):\n      cache-tree: drop `the_repository` in `cache_tree_fully_valid()`\n      cache-tree: remove dependency on `the_repository`\n      submodule-config: remove uses of `the_repository`\n      submodule-config: stop using `the_hash_algo`\n      submodule-config: stop registering submodule sources\n      builtin/grep: stop registering submodule ODB as source\n      odb: remove infrastructure to register submodule sources\n      tmp-objdir: drop unused function to register alternate\n      odb/packed: fix memory leaks when freeing source\n      builtin/multi-pack-index: refuse unknown sources with \"--object-dir=\"\n      t/helper: adapt read-midx to not link ad-hoc source anymore\n      t/helper: stop registering alternates in \"ref-store\" command\n      odb: remove the ability to link sources ad-hoc\n\n builtin/checkout.c                     |  2 +-\n builtin/commit.c                       |  2 +-\n builtin/fetch.c                        |  2 +-\n builtin/grep.c                         | 28 +++-------\n builtin/multi-pack-index.c             |  3 +-\n builtin/submodule--helper.c            |  8 +--\n cache-tree.c                           | 95 +++++++++++++++++++---------------\n cache-tree.h                           |  7 +--\n odb.c                                  | 42 ---------------\n odb.h                                  | 22 --------\n odb/source-packed.c                    |  1 +\n read-cache-ll.h                        |  5 +-\n read-cache.c                           |  9 ++--\n sequencer.c                            |  2 +-\n sparse-index.c                         |  2 +-\n submodule-config.c                     | 59 +++++++++++----------\n submodule-config.h                     | 12 +++--\n submodule.c                            |  2 +-\n t/README                               |  7 ---\n t/helper/test-read-midx.c              | 43 ++++++++++-----\n t/helper/test-ref-store.c              |  8 ---\n t/helper/test-submodule.c              |  4 +-\n t/t5319-multi-pack-index.sh            |  9 ++--\n t/t5526-fetch-submodules.sh            |  3 --\n t/t5531-deep-submodule-push.sh         |  3 --\n t/t5545-push-options.sh                |  3 --\n t/t5572-pull-submodule.sh              |  3 --\n t/t6437-submodule-merge.sh             |  3 --\n t/t7418-submodule-sparse-gitmodules.sh |  3 --\n t/t7814-grep-recurse-submodules.sh     |  3 --\n tmp-objdir.c                           |  5 --\n tmp-objdir.h                           |  6 ---\n unpack-trees.c                         |  9 ++--\n 33 files changed, 168 insertions(+), 247 deletions(-)\n\nRange-diff versus v2:\n\n 1:  95ff726928 =  1:  a785e29859 cache-tree: drop `the_repository` in `cache_tree_fully_valid()`\n 2:  2b2cf592c4 =  2:  002cca01eb cache-tree: remove dependency on `the_repository`\n 3:  7727c40da8 =  3:  17ab3e4553 submodule-config: remove uses of `the_repository`\n 4:  6c2168af46 =  4:  586a786dc6 submodule-config: stop using `the_hash_algo`\n 5:  60c1c9558d !  5:  539dd9f9d5 submodule-config: stop registering submodule sources\n    @@ Commit message\n         submodule-config: stop registering submodule sources\n     \n         When reading the \".gitmodules\" file from a blob in a repository other\n    -    than `the_repository`, we register the repository's object database as\n    +    than `the_repository`, we register that repository's object database as\n         an in-memory source of `the_repository`'s object database. This call has\n         its origins in d9b8b8f896 (submodule-config.c: use repo_get_oid for\n         reading .gitmodules, 2019-04-16): back then, `config_with_options()` was\n 6:  7f5354c2bf =  6:  11db0ac853 builtin/grep: stop registering submodule ODB as source\n 7:  24e5a2a3b7 =  7:  5d4660657c odb: remove infrastructure to register submodule sources\n 8:  c07db4254f =  8:  577e58d475 tmp-objdir: drop unused function to register alternate\n 9:  e1022b26a6 =  9:  00bc8549a9 odb/packed: fix memory leaks when freeing source\n10:  8c5c827595 = 10:  d66457c73a builtin/multi-pack-index: refuse unknown sources with \"--object-dir=\"\n11:  7d7cb53936 = 11:  7d13925a93 t/helper: adapt read-midx to not link ad-hoc source anymore\n12:  537a58d286 = 12:  6a0092d870 t/helper: stop registering alternates in \"ref-store\" command\n13:  d704faeb44 = 13:  4e446e8a51 odb: remove the ability to link sources ad-hoc\n\n---\nbase-commit: e5d60560f61f520e9ea350645a6cc9770b0f1607\nchange-id: 20260811-pks-odb-registering-in-memory-sources-88648cd95735\n\n"},{"id":"552502","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-1-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 01/13] cache-tree: drop `the_repository` in `cache_tree_fully_valid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:44Z","receivedAt":"2026-09-11T05:51:52Z","isPatch":true,"body":"The function `cache_tree_fully_valid()` verifies whether the cache tree\nowned by the index is valid or not. As part of that, the function checks\nwhether the objects referenced by the cache all exist. But because the\nfunction has no repository available, it is using the object database of\n`the_repository` instead.\n\nWe could of course adapt callers to pass in a repository as parameter\nexplicitly to get rid of this implicit dependency on global state. But\nall of them pass the cache tree owned by a `struct index_state`, and\nthat structure already has a reference to its owning repository.\n\nSo instead, adapt the function to accept a `struct index_state`, which\nensures that callers will implicitly always pass the correct repository.\nAdapt callers accordingly.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/checkout.c |  2 +-\n builtin/commit.c   |  2 +-\n cache-tree.c       | 17 ++++++++++++-----\n cache-tree.h       |  2 +-\n sequencer.c        |  2 +-\n sparse-index.c     |  2 +-\n unpack-trees.c     |  2 +-\n 7 files changed, 18 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 55e3a89a85..505d3f7bf3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -921,7 +921,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t}\n \t}\n \n-\tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n+\tif (!cache_tree_fully_valid(the_repository->index))\n \t\tcache_tree_update(the_repository->index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);\n \n \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 28f6174503..840b6b4083 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -484,7 +484,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\t\t\t       LOCK_DIE_ON_ERROR);\n \t\trefresh_cache_or_die(refresh_flags);\n \t\tif (the_repository->index->cache_changed\n-\t\t    || !cache_tree_fully_valid(the_repository->index->cache_tree))\n+\t\t    || !cache_tree_fully_valid(the_repository->index))\n \t\t\tcache_tree_update(the_repository->index, WRITE_TREE_SILENT);\n \t\tif (write_locked_index(the_repository->index, &index_lock,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\ndiff --git a/cache-tree.c b/cache-tree.c\nindex a220372a42..6103b3fcb3 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -275,22 +275,29 @@ static void discard_unused_subtrees(struct cache_tree *it)\n \t}\n }\n \n-int cache_tree_fully_valid(struct cache_tree *it)\n+static int cache_tree_fully_valid_recursive(struct object_database *odb,\n+\t\t\t\t\t    struct cache_tree *it)\n {\n \tint i;\n \tif (!it)\n \t\treturn 0;\n \tif (it->entry_count < 0 ||\n-\t    !odb_has_object(the_repository->objects, &it->oid,\n+\t    !odb_has_object(odb, &it->oid,\n \t\t\t    ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))\n \t\treturn 0;\n \tfor (i = 0; i < it->subtree_nr; i++) {\n-\t\tif (!cache_tree_fully_valid(it->down[i]->cache_tree))\n+\t\tif (!cache_tree_fully_valid_recursive(odb, it->down[i]->cache_tree))\n \t\t\treturn 0;\n \t}\n \treturn 1;\n }\n \n+int cache_tree_fully_valid(struct index_state *istate)\n+{\n+\treturn cache_tree_fully_valid_recursive(istate->repo->objects,\n+\t\t\t\t\t\tistate->cache_tree);\n+}\n+\n static int must_check_existence(const struct cache_entry *ce)\n {\n \treturn !(repo_has_promisor_remote(the_repository) && ce_skip_worktree(ce));\n@@ -775,7 +782,7 @@ struct tree *write_in_core_index_as_tree(struct repository *repo,\n \tint was_valid, ret;\n \n \twas_valid = index_state->cache_tree &&\n-\t\t    cache_tree_fully_valid(index_state->cache_tree);\n+\t\t    cache_tree_fully_valid(index_state);\n \n \tret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL);\n \tif (ret == WRITE_TREE_UNMERGED_INDEX) {\n@@ -811,7 +818,7 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \n \twas_valid = !(flags & WRITE_TREE_IGNORE_CACHE_TREE) &&\n \t\t    index_state->cache_tree &&\n-\t\t    cache_tree_fully_valid(index_state->cache_tree);\n+\t\t    cache_tree_fully_valid(index_state);\n \n \tret = write_index_as_tree_internal(oid, index_state, was_valid, flags,\n \t\t\t\t\t   prefix);\ndiff --git a/cache-tree.h b/cache-tree.h\nindex f8bddae523..4b3f60d6db 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -31,7 +31,7 @@ int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen)\n void cache_tree_write(struct strbuf *, struct cache_tree *root);\n struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);\n \n-int cache_tree_fully_valid(struct cache_tree *);\n+int cache_tree_fully_valid(struct index_state *);\n int cache_tree_update(struct index_state *, int);\n int cache_tree_verify(struct repository *, struct index_state *);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 65afd100d9..11a95c031b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -814,7 +814,7 @@ static int do_recursive_merge(struct repository *r,\n \n static struct object_id *get_cache_tree_oid(struct index_state *istate)\n {\n-\tif (!cache_tree_fully_valid(istate->cache_tree))\n+\tif (!cache_tree_fully_valid(istate))\n \t\tif (cache_tree_update(istate, 0)) {\n \t\t\terror(_(\"unable to update cache tree\"));\n \t\t\treturn NULL;\ndiff --git a/sparse-index.c b/sparse-index.c\nindex c1fa231a89..3d77dadae5 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -228,7 +228,7 @@ int convert_to_sparse(struct index_state *istate, int flags)\n \tif (index_has_unmerged_entries(istate))\n \t\treturn 0;\n \n-\tif (!cache_tree_fully_valid(istate->cache_tree)) {\n+\tif (!cache_tree_fully_valid(istate)) {\n \t\t/* Clear and recompute the cache-tree */\n \t\tcache_tree_free(&istate->cache_tree);\n \ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 154d6d40a1..f6bb1e6d2b 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -2086,7 +2086,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\t\t}\n \n \t\t\tif (!o->skip_cache_tree_update &&\n-\t\t\t    !cache_tree_fully_valid(o->internal.result.cache_tree))\n+\t\t\t    !cache_tree_fully_valid(&o->internal.result))\n \t\t\t\tcache_tree_update(&o->internal.result,\n \t\t\t\t\t\t  WRITE_TREE_SILENT |\n \t\t\t\t\t\t  WRITE_TREE_REPAIR);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552503","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-2-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 02/13] cache-tree: remove dependency on `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:45Z","receivedAt":"2026-09-11T05:51:56Z","isPatch":true,"body":"The \"cache-tree\" subsystem still depends on `the_repository`. Adapt it\nto instead use repositories provided via the context, either as a new\nparameter or the one passed in via `struct index_state`.\n\nBesides getting rid of `the_repository`, this also removes the last\ndependency on registering submodule sources with the main object\ndatabase. When reading gitmodules from a submodule's index we implicitly\nread that object via `the_repository`'s object database, which is of\ncourse wrong. This works though because we would then register the\nsubmodule's object database with the main object database, but a later\npatch is going to get rid of that mechanism.\n\nYou can verify that we indeed no longer depend on this mechanism by\nrunning tests with `GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=true`. Without\nthis patch we fail in t1092, with this patch we never register submodule\nobject databases anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n cache-tree.c    | 78 +++++++++++++++++++++++++++++++--------------------------\n cache-tree.h    |  5 ++--\n read-cache-ll.h |  5 ++--\n read-cache.c    |  9 ++++---\n unpack-trees.c  |  7 +++---\n 5 files changed, 57 insertions(+), 47 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 6103b3fcb3..b8cbb5da22 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\n@@ -298,12 +297,14 @@ int cache_tree_fully_valid(struct index_state *istate)\n \t\t\t\t\t\tistate->cache_tree);\n }\n \n-static int must_check_existence(const struct cache_entry *ce)\n+static int must_check_existence(const struct cache_entry *ce, void *cb_data)\n {\n-\treturn !(repo_has_promisor_remote(the_repository) && ce_skip_worktree(ce));\n+\tstruct repository *repo = cb_data;\n+\treturn !(repo_has_promisor_remote(repo) && ce_skip_worktree(ce));\n }\n \n-static int update_one(struct cache_tree *it,\n+static int update_one(struct repository *repo,\n+\t\t      struct cache_tree *it,\n \t\t      struct cache_entry **cache,\n \t\t      int entries,\n \t\t      const char *base,\n@@ -341,7 +342,7 @@ static int update_one(struct cache_tree *it,\n \t}\n \n \tif (0 <= it->entry_count &&\n-\t    odb_has_object(the_repository->objects, &it->oid,\n+\t    odb_has_object(repo->objects, &it->oid,\n \t\t\t   ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))\n \t\treturn it->entry_count;\n \n@@ -382,7 +383,8 @@ static int update_one(struct cache_tree *it,\n \t\tsub = find_subtree(it, path + baselen, sublen, 1);\n \t\tif (!sub->cache_tree)\n \t\t\tsub->cache_tree = cache_tree();\n-\t\tsubcnt = update_one(sub->cache_tree,\n+\t\tsubcnt = update_one(repo,\n+\t\t\t\t    sub->cache_tree,\n \t\t\t\t    cache + i, entries - i,\n \t\t\t\t    path,\n \t\t\t\t    baselen + sublen + 1,\n@@ -446,10 +448,10 @@ static int update_one(struct cache_tree *it,\n \t\t}\n \n \t\tce_missing_ok = mode == S_IFGITLINK || missing_ok ||\n-\t\t\t!must_check_existence(ce);\n+\t\t\t!must_check_existence(ce, repo);\n \t\tif (is_null_oid(oid) ||\n \t\t    (!ce_missing_ok &&\n-\t\t     !odb_has_object(the_repository->objects, oid,\n+\t\t     !odb_has_object(repo->objects, oid,\n \t\t\t\t     ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))) {\n \t\t\tstrbuf_release(&buffer);\n \t\t\tif (expected_missing)\n@@ -481,12 +483,12 @@ static int update_one(struct cache_tree *it,\n \t\t/*\n \t\t * \"sub\" can be an empty tree if all subentries are i-t-a.\n \t\t */\n-\t\tif (contains_ita && is_empty_tree_oid(oid, the_repository->hash_algo))\n+\t\tif (contains_ita && is_empty_tree_oid(oid, repo->hash_algo))\n \t\t\tcontinue;\n \n \t\tstrbuf_grow(&buffer, entlen + 100);\n \t\tstrbuf_addf(&buffer, \"%o %.*s%c\", mode, entlen, path + baselen, '\\0');\n-\t\tstrbuf_add(&buffer, oid->hash, the_hash_algo->rawsz);\n+\t\tstrbuf_add(&buffer, oid->hash, repo->hash_algo->rawsz);\n \n #if DEBUG_CACHE_TREE\n \t\tfprintf(stderr, \"cache-tree update-one %o %.*s\\n\",\n@@ -496,16 +498,16 @@ static int update_one(struct cache_tree *it,\n \n \tif (repair) {\n \t\tstruct object_id oid;\n-\t\thash_object_file(the_hash_algo, buffer.buf, buffer.len,\n+\t\thash_object_file(repo->hash_algo, buffer.buf, buffer.len,\n \t\t\t\t OBJ_TREE, &oid);\n-\t\tif (odb_has_object(the_repository->objects, &oid, ODB_HAS_OBJECT_RECHECK_PACKED))\n+\t\tif (odb_has_object(repo->objects, &oid, ODB_HAS_OBJECT_RECHECK_PACKED))\n \t\t\toidcpy(&it->oid, &oid);\n \t\telse\n \t\t\tto_invalidate = 1;\n \t} else if (dryrun) {\n-\t\thash_object_file(the_hash_algo, buffer.buf, buffer.len,\n+\t\thash_object_file(repo->hash_algo, buffer.buf, buffer.len,\n \t\t\t\t OBJ_TREE, &it->oid);\n-\t} else if (odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len, OBJ_TREE,\n+\t} else if (odb_write_object_ext(repo->objects, buffer.buf, buffer.len, OBJ_TREE,\n \t\t\t\t\t&it->oid, NULL, flags & WRITE_TREE_SILENT ? ODB_WRITE_OBJECT_SILENT : 0)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn -1;\n@@ -523,7 +525,7 @@ static int update_one(struct cache_tree *it,\n \n int cache_tree_update(struct index_state *istate, int flags)\n {\n-\tint inflight = !!the_repository->objects->transaction;\n+\tint inflight = !!istate->repo->objects->transaction;\n \tstruct odb_transaction *transaction;\n \tint skip, i;\n \n@@ -535,14 +537,14 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \n-\tif (!(flags & WRITE_TREE_MISSING_OK) && repo_has_promisor_remote(the_repository))\n-\t\tprefetch_cache_entries(istate, must_check_existence);\n+\tif (!(flags & WRITE_TREE_MISSING_OK) && repo_has_promisor_remote(istate->repo))\n+\t\tprefetch_cache_entries(istate, must_check_existence, istate->repo);\n \n \ttrace_performance_enter();\n \ttrace2_region_enter(\"cache_tree\", \"update\", istate->repo);\n \tif (!inflight)\n-\t\todb_transaction_begin_or_die(the_repository->objects, &transaction, 0);\n-\ti = update_one(istate->cache_tree, istate->cache, istate->cache_nr,\n+\t\todb_transaction_begin_or_die(istate->repo->objects, &transaction, 0);\n+\ti = update_one(istate->repo, istate->cache_tree, istate->cache, istate->cache_nr,\n \t\t       \"\", 0, &skip, flags);\n \tif (!inflight)\n \t\todb_transaction_commit_and_finalize_or_die(transaction);\n@@ -554,7 +556,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \treturn 0;\n }\n \n-static void write_one(struct strbuf *buffer, struct cache_tree *it,\n+static void write_one(struct repository *repo,\n+\t\t      struct strbuf *buffer, struct cache_tree *it,\n \t\t      const char *path, int pathlen)\n {\n \tint i;\n@@ -580,7 +583,7 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it,\n #endif\n \n \tif (0 <= it->entry_count) {\n-\t\tstrbuf_add(buffer, it->oid.hash, the_hash_algo->rawsz);\n+\t\tstrbuf_add(buffer, it->oid.hash, repo->hash_algo->rawsz);\n \t}\n \tfor (i = 0; i < it->subtree_nr; i++) {\n \t\tstruct cache_tree_sub *down = it->down[i];\n@@ -590,15 +593,16 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it,\n \t\t\t\t\t     prev->name, prev->namelen) <= 0)\n \t\t\t\tdie(\"fatal - unsorted cache subtree\");\n \t\t}\n-\t\twrite_one(buffer, down->cache_tree, down->name, down->namelen);\n+\t\twrite_one(repo, buffer, down->cache_tree, down->name, down->namelen);\n \t}\n }\n \n-void cache_tree_write(struct strbuf *sb, struct cache_tree *root)\n+void cache_tree_write(struct repository *repo,\n+\t\t      struct strbuf *sb, struct cache_tree *root)\n {\n-\ttrace2_region_enter(\"cache_tree\", \"write\", the_repository);\n-\twrite_one(sb, root, \"\", 0);\n-\ttrace2_region_leave(\"cache_tree\", \"write\", the_repository);\n+\ttrace2_region_enter(\"cache_tree\", \"write\", repo);\n+\twrite_one(repo, sb, root, \"\", 0);\n+\ttrace2_region_leave(\"cache_tree\", \"write\", repo);\n }\n \n static int parse_int(const char **ptr, unsigned long *len_p, int *out)\n@@ -632,13 +636,14 @@ static int parse_int(const char **ptr, unsigned long *len_p, int *out)\n \treturn 0;\n }\n \n-static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n+static struct cache_tree *read_one(struct repository *repo,\n+\t\t\t\t   const char **buffer, unsigned long *size_p)\n {\n \tconst char *buf = *buffer;\n \tunsigned long size = *size_p;\n \tstruct cache_tree *it;\n \tint i, subtree_nr;\n-\tconst unsigned rawsz = the_hash_algo->rawsz;\n+\tconst unsigned rawsz = repo->hash_algo->rawsz;\n \n \tit = NULL;\n \t/* skip name, but make sure name exists */\n@@ -665,7 +670,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n \t\tif (size < rawsz)\n \t\t\tgoto free_return;\n \t\toidread(&it->oid, (const unsigned char *)buf,\n-\t\t\tthe_repository->hash_algo);\n+\t\t\trepo->hash_algo);\n \t\tbuf += rawsz;\n \t\tsize -= rawsz;\n \t}\n@@ -693,7 +698,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n \t\tstruct cache_tree_sub *subtree;\n \t\tconst char *name = buf;\n \n-\t\tsub = read_one(&buf, &size);\n+\t\tsub = read_one(repo, &buf, &size);\n \t\tif (!sub)\n \t\t\tgoto free_return;\n \t\tsubtree = cache_tree_sub(it, name);\n@@ -710,16 +715,17 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n \treturn NULL;\n }\n \n-struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)\n+struct cache_tree *cache_tree_read(struct repository *repo,\n+\t\t\t\t   const char *buffer, unsigned long size)\n {\n \tstruct cache_tree *result;\n \n \tif (buffer[0])\n \t\treturn NULL; /* not the whole tree */\n \n-\ttrace2_region_enter(\"cache_tree\", \"read\", the_repository);\n-\tresult = read_one(&buffer, &size);\n-\ttrace2_region_leave(\"cache_tree\", \"read\", the_repository);\n+\ttrace2_region_enter(\"cache_tree\", \"read\", repo);\n+\tresult = read_one(repo, &buffer, &size);\n+\ttrace2_region_leave(\"cache_tree\", \"read\", repo);\n \n \treturn result;\n }\n@@ -810,7 +816,7 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \thold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);\n \n \tentries = read_index_from(index_state, index_path,\n-\t\t\t\t  repo_get_git_dir(the_repository));\n+\t\t\t\t  repo_get_git_dir(index_state->repo));\n \tif (entries < 0) {\n \t\tret = WRITE_TREE_UNREADABLE_INDEX;\n \t\tgoto out;\n@@ -866,7 +872,7 @@ static void prime_cache_tree_rec(struct repository *r,\n \t\t\tstruct cache_tree_sub *sub;\n \t\t\tstruct tree *subtree = lookup_tree(r, &entry.oid);\n \n-\t\t\tif (repo_parse_tree(the_repository, subtree) < 0)\n+\t\t\tif (repo_parse_tree(r, subtree) < 0)\n \t\t\t\texit(128);\n \t\t\tsub = cache_tree_sub(it, entry.path);\n \t\t\tsub->cache_tree = cache_tree();\ndiff --git a/cache-tree.h b/cache-tree.h\nindex 4b3f60d6db..7a2177de83 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -28,8 +28,9 @@ struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);\n \n int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen);\n \n-void cache_tree_write(struct strbuf *, struct cache_tree *root);\n-struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);\n+void cache_tree_write(struct repository *repo, struct strbuf *, struct cache_tree *root);\n+struct cache_tree *cache_tree_read(struct repository *repo,\n+\t\t\t\t   const char *buffer, unsigned long size);\n \n int cache_tree_fully_valid(struct index_state *);\n int cache_tree_update(struct index_state *, int);\ndiff --git a/read-cache-ll.h b/read-cache-ll.h\nindex 8eb266cfd1..066dd8bc3b 100644\n--- a/read-cache-ll.h\n+++ b/read-cache-ll.h\n@@ -269,9 +269,10 @@ void validate_cache_entries(const struct index_state *istate);\n  * the given predicate. This function should only be called if\n  * repo_has_promisor_remote() returns true.\n  */\n-typedef int (*must_prefetch_predicate)(const struct cache_entry *);\n+typedef int (*must_prefetch_predicate)(const struct cache_entry *, void *cb_data);\n void prefetch_cache_entries(const struct index_state *istate,\n-\t\t\t    must_prefetch_predicate must_prefetch);\n+\t\t\t    must_prefetch_predicate must_prefetch,\n+\t\t\t    void *cb_data);\n \n /* Initialize and use the cache information */\n struct lock_file;\ndiff --git a/read-cache.c b/read-cache.c\nindex 8044ff820b..e40f290bb3 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1748,7 +1748,7 @@ static int read_index_extension(struct index_state *istate,\n {\n \tswitch (CACHE_EXT(ext)) {\n \tcase CACHE_EXT_TREE:\n-\t\tistate->cache_tree = cache_tree_read(data, sz);\n+\t\tistate->cache_tree = cache_tree_read(istate->repo, data, sz);\n \t\tbreak;\n \tcase CACHE_EXT_RESOLVE_UNDO:\n \t\tistate->resolve_undo = resolve_undo_read(data, sz, the_hash_algo);\n@@ -3012,7 +3012,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t    !drop_cache_tree && istate->cache_tree) {\n \t\tstrbuf_reset(&sb);\n \n-\t\tcache_tree_write(&sb, istate->cache_tree);\n+\t\tcache_tree_write(istate->repo, &sb, istate->cache_tree);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n \t\tif (err) {\n@@ -3733,7 +3733,8 @@ static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_ta\n }\n \n void prefetch_cache_entries(const struct index_state *istate,\n-\t\t\t    must_prefetch_predicate must_prefetch)\n+\t\t\t    must_prefetch_predicate must_prefetch,\n+\t\t\t    void *cb_data)\n {\n \tint i;\n \tstruct oid_array to_fetch = OID_ARRAY_INIT;\n@@ -3741,7 +3742,7 @@ void prefetch_cache_entries(const struct index_state *istate,\n \tfor (i = 0; i < istate->cache_nr; i++) {\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n-\t\tif (S_ISGITLINK(ce->ce_mode) || !must_prefetch(ce))\n+\t\tif (S_ISGITLINK(ce->ce_mode) || !must_prefetch(ce, cb_data))\n \t\t\tcontinue;\n \t\tif (!odb_read_object_info_extended(the_repository->objects,\n \t\t\t\t\t\t   &ce->oid, NULL,\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex f6bb1e6d2b..1802809ad3 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -416,7 +416,8 @@ static void report_collided_checkout(struct index_state *index)\n \tstring_list_clear(&list, 0);\n }\n \n-static int must_checkout(const struct cache_entry *ce)\n+static int must_checkout(const struct cache_entry *ce,\n+\t\t\t void *cb_data UNUSED)\n {\n \treturn ce->ce_flags & CE_UPDATE;\n }\n@@ -477,7 +478,7 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t * Prefetch the objects that are to be checked out in the loop\n \t\t * below.\n \t\t */\n-\t\tprefetch_cache_entries(index, must_checkout);\n+\t\tprefetch_cache_entries(index, must_checkout, NULL);\n \n \tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n \n@@ -487,7 +488,7 @@ static int check_updates(struct unpack_trees_options *o,\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n-\t\tif (must_checkout(ce)) {\n+\t\tif (must_checkout(ce, NULL)) {\n \t\t\tsize_t last_pc_queue_size = pc_queue_size();\n \n \t\t\tif (ce->ce_flags & CE_WT_REMOVE)\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552504","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-3-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 03/13] submodule-config: remove uses of `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:46Z","receivedAt":"2026-09-11T05:51:59Z","isPatch":true,"body":"Several functions in the submodule-config subsystem implicitly depend\non `the_repository`. Refactor these to take a `struct repository` as\nparameter and adapt callers accordingly.\n\nNote that as usual with these refactorings, callers simply pass\n`the_repository` even if they already have a different repository\navailable in the calling context. This simplifies the migration and\nensures that we don't have a change in behaviour.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fetch.c             |  2 +-\n builtin/grep.c              |  2 +-\n builtin/submodule--helper.c |  8 ++++----\n submodule-config.c          | 49 ++++++++++++++++++++++++++-------------------\n submodule-config.h          | 12 +++++++----\n submodule.c                 |  2 +-\n t/helper/test-submodule.c   |  4 ++--\n 7 files changed, 45 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ab7db2be06..533fdfe7d8 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -2681,7 +2681,7 @@ int cmd_fetch(int argc,\n \t\tint *rs = config.recurse_submodules == RECURSE_SUBMODULES_DEFAULT\n \t\t\t  ? &config.recurse_submodules : NULL;\n \n-\t\tfetch_config_from_gitmodules(sfjc, rs);\n+\t\tfetch_config_from_gitmodules(the_repository, sfjc, rs);\n \t}\n \n \ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex d3d86abe01..073dfaaf45 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -897,7 +897,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(&real_obj->oid);\n+\t\t\tgitmodules_config_oid(the_repository, &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,\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex e7cd3225fa..aaaa963fd8 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -3041,7 +3041,7 @@ static int module_update(int argc, const char **argv, const char *prefix,\n \t\tNULL\n \t};\n \n-\tupdate_clone_config_from_gitmodules(&opt.max_jobs);\n+\tupdate_clone_config_from_gitmodules(the_repository, &opt.max_jobs);\n \trepo_config(the_repository, git_update_clone_config, &opt.max_jobs);\n \n \targc = parse_options(argc, argv, prefix, module_update_options,\n@@ -3255,7 +3255,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix,\n \t\t    path);\n \n \tconfig_name = xstrfmt(\"submodule.%s.url\", sub->name);\n-\tret = config_set_in_gitmodules_file_gently(config_name, newurl);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, config_name, newurl);\n \n \tif (!ret) {\n \t\trepo_read_gitmodules(the_repository, 0);\n@@ -3311,7 +3311,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix,\n \t\t    path);\n \n \tconfig_name = xstrfmt(\"submodule.%s.branch\", sub->name);\n-\tret = config_set_in_gitmodules_file_gently(config_name, opt_branch);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, config_name, opt_branch);\n \n \tfree(config_name);\n \treturn !!ret;\n@@ -3510,7 +3510,7 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con\n \t\tdie(_(\"please make sure that the .gitmodules file is in the working tree\"));\n \n \tkey = xstrfmt(\"submodule.%s.%s\", name, var);\n-\tret = config_set_in_gitmodules_file_gently(key, value);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, key, value);\n \tfree(key);\n \n \treturn ret;\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f75997402a..f8c2cf7a93 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -667,19 +667,20 @@ static int parse_config(const char *var, const char *value,\n \treturn ret;\n }\n \n-static int gitmodule_oid_from_commit(const struct object_id *treeish_name,\n+static int gitmodule_oid_from_commit(struct repository *repo,\n+\t\t\t\t     const struct object_id *treeish_name,\n \t\t\t\t     struct object_id *gitmodules_oid,\n \t\t\t\t     struct strbuf *rev)\n {\n \tint ret = 0;\n \n \tif (is_null_oid(treeish_name)) {\n-\t\toidclr(gitmodules_oid, the_repository->hash_algo);\n+\t\toidclr(gitmodules_oid, repo->hash_algo);\n \t\treturn 1;\n \t}\n \n \tstrbuf_addf(rev, \"%s:.gitmodules\", oid_to_hex(treeish_name));\n-\tif (repo_get_oid(the_repository, rev->buf, gitmodules_oid) >= 0)\n+\tif (repo_get_oid(repo, rev->buf, gitmodules_oid) >= 0)\n \t\tret = 1;\n \n \treturn ret;\n@@ -689,9 +690,11 @@ static int gitmodule_oid_from_commit(const struct object_id *treeish_name,\n  * (key) with on-demand reading of the appropriate .gitmodules from\n  * revisions.\n  */\n-static const struct submodule *config_from(struct submodule_cache *cache,\n-\t\tconst struct object_id *treeish_name, const char *key,\n-\t\tenum lookup_type lookup_type)\n+static const struct submodule *config_from(struct repository *repo,\n+\t\t\t\t\t   struct submodule_cache *cache,\n+\t\t\t\t\t   const struct object_id *treeish_name,\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   enum lookup_type lookup_type)\n {\n \tstruct strbuf rev = STRBUF_INIT;\n \tsize_t config_size;\n@@ -718,7 +721,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \t\treturn entry->config;\n \t}\n \n-\tif (!gitmodule_oid_from_commit(treeish_name, &oid, &rev))\n+\tif (!gitmodule_oid_from_commit(repo, treeish_name, &oid, &rev))\n \t\tgoto out;\n \n \tswitch (lookup_type) {\n@@ -732,7 +735,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tif (submodule)\n \t\tgoto out;\n \n-\tconfig = odb_read_object(the_repository->objects, &oid,\n+\tconfig = odb_read_object(repo->objects, &oid,\n \t\t\t\t &type, &config_size);\n \tif (!config || type != OBJ_BLOB)\n \t\tgoto out;\n@@ -843,21 +846,22 @@ void repo_read_gitmodules(struct repository *repo, int skip_if_read)\n \trepo->submodule_cache->gitmodules_read = 1;\n }\n \n-void gitmodules_config_oid(const struct object_id *commit_oid)\n+void gitmodules_config_oid(struct repository *repo,\n+\t\t\t   const struct object_id *commit_oid)\n {\n \tstruct strbuf rev = STRBUF_INIT;\n \tstruct object_id oid;\n \n-\tsubmodule_cache_check_init(the_repository);\n+\tsubmodule_cache_check_init(repo);\n \n-\tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n+\tif (gitmodule_oid_from_commit(repo, commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t repo, &oid, repo,\n \t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-\tthe_repository->submodule_cache->gitmodules_read = 1;\n+\trepo->submodule_cache->gitmodules_read = 1;\n }\n \n const struct submodule *submodule_from_name(struct repository *r,\n@@ -865,7 +869,7 @@ const struct submodule *submodule_from_name(struct repository *r,\n \t\tconst char *name)\n {\n \trepo_read_gitmodules(r, 1);\n-\treturn config_from(r->submodule_cache, treeish_name, name, lookup_name);\n+\treturn config_from(r, r->submodule_cache, treeish_name, name, lookup_name);\n }\n \n const struct submodule *submodule_from_path(struct repository *r,\n@@ -873,7 +877,7 @@ const struct submodule *submodule_from_path(struct repository *r,\n \t\tconst char *path)\n {\n \trepo_read_gitmodules(r, 1);\n-\treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n+\treturn config_from(r, r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n /**\n@@ -980,11 +984,12 @@ int print_config_from_gitmodules(struct repository *repo, const char *key)\n \treturn 0;\n }\n \n-int config_set_in_gitmodules_file_gently(const char *key, const char *value)\n+int config_set_in_gitmodules_file_gently(struct repository *repo,\n+\t\t\t\t\t const char *key, const char *value)\n {\n \tint ret;\n \n-\tret = repo_config_set_in_file_gently(the_repository, GITMODULES_FILE, key, NULL, value);\n+\tret = repo_config_set_in_file_gently(repo, GITMODULES_FILE, key, NULL, value);\n \tif (ret < 0)\n \t\t/* Maybe the user already did that, don't error out here */\n \t\twarning(_(\"Could not update .gitmodules entry %s\"), key);\n@@ -1017,13 +1022,15 @@ static int gitmodules_fetch_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n+void fetch_config_from_gitmodules(struct repository *repo,\n+\t\t\t\t  int *max_children,\n+\t\t\t\t  int *recurse_submodules)\n {\n \tstruct fetch_config config = {\n \t\t.max_children = max_children,\n \t\t.recurse_submodules = recurse_submodules\n \t};\n-\tconfig_from_gitmodules(gitmodules_fetch_config, the_repository, &config);\n+\tconfig_from_gitmodules(gitmodules_fetch_config, repo, &config);\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n@@ -1036,7 +1043,7 @@ static int gitmodules_update_clone_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-void update_clone_config_from_gitmodules(int *max_jobs)\n+void update_clone_config_from_gitmodules(struct repository *repo, int *max_jobs)\n {\n-\tconfig_from_gitmodules(gitmodules_update_clone_config, the_repository, max_jobs);\n+\tconfig_from_gitmodules(gitmodules_update_clone_config, repo, max_jobs);\n }\ndiff --git a/submodule-config.h b/submodule-config.h\nindex f55d4e3b61..755570d5d1 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -57,7 +57,8 @@ int option_fetch_parse_recurse_submodules(const struct option *opt,\n int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n int parse_push_recurse_submodules_arg(const char *opt, const char *arg);\n void repo_read_gitmodules(struct repository *repo, int skip_if_read);\n-void gitmodules_config_oid(const struct object_id *commit_oid);\n+void gitmodules_config_oid(struct repository *repo,\n+\t\t\t   const struct object_id *commit_oid);\n \n /**\n  * Same as submodule_from_path but lookup by name.\n@@ -80,7 +81,8 @@ const struct submodule *submodule_from_path(struct repository *r,\n void submodule_free(struct repository *r);\n \n int print_config_from_gitmodules(struct repository *repo, const char *key);\n-int config_set_in_gitmodules_file_gently(const char *key, const char *value);\n+int config_set_in_gitmodules_file_gently(struct repository *repo,\n+\t\t\t\t\t const char *key, const char *value);\n \n /*\n  * Returns 0 if the name is syntactically acceptable as a submodule \"name\"\n@@ -100,8 +102,10 @@ int check_submodule_url(const char *url);\n  * New helpers to retrieve arbitrary configuration from the '.gitmodules' file\n  * should NOT be added.\n  */\n-void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n-void update_clone_config_from_gitmodules(int *max_jobs);\n+void fetch_config_from_gitmodules(struct repository *repo,\n+\t\t\t\t  int *max_children,\n+\t\t\t\t  int *recurse_submodules);\n+void update_clone_config_from_gitmodules(struct repository *repo, int *max_jobs);\n \n /*\n  * Submodule entry that contains relevant information about a\ndiff --git a/submodule.c b/submodule.c\nindex 5c92575888..6fcb606f7e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -133,7 +133,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath)\n \tstrbuf_addstr(&entry, \"submodule.\");\n \tstrbuf_addstr(&entry, submodule->name);\n \tstrbuf_addstr(&entry, \".path\");\n-\tret = config_set_in_gitmodules_file_gently(entry.buf, newpath);\n+\tret = config_set_in_gitmodules_file_gently(the_repository, entry.buf, newpath);\n \tstrbuf_release(&entry);\n \treturn ret;\n }\ndiff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c\nindex 3c5c4c4a09..ea9bef0904 100644\n--- a/t/helper/test-submodule.c\n+++ b/t/helper/test-submodule.c\n@@ -168,7 +168,7 @@ static int cmd__submodule_config_set(int argc, const char **argv)\n \t\tif (!is_writing_gitmodules_ok())\n \t\t\tdie(\"please make sure that the .gitmodules file is in the working tree\");\n \n-\t\treturn config_set_in_gitmodules_file_gently(argv[1], argv[2]);\n+\t\treturn config_set_in_gitmodules_file_gently(the_repository, argv[1], argv[2]);\n \t}\n \tusage_with_options(usage, options);\n }\n@@ -188,7 +188,7 @@ static int cmd__submodule_config_unset(int argc, const char **argv)\n \tif (argc == 2) {\n \t\tif (!is_writing_gitmodules_ok())\n \t\t\tdie(\"please make sure that the .gitmodules file is in the working tree\");\n-\t\treturn config_set_in_gitmodules_file_gently(argv[1], NULL);\n+\t\treturn config_set_in_gitmodules_file_gently(the_repository, argv[1], NULL);\n \t}\n \tusage_with_options(usage, options);\n }\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552505","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-4-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 04/13] submodule-config: stop using `the_hash_algo`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:47Z","receivedAt":"2026-09-11T05:52:02Z","isPatch":true,"body":"We have two uses of `the_hash_algo` in \"submodule-config.c\":\n\n  - One trivial use in `gitmodules_cb`, which we can convert to use the\n    hash algorithm of the repository that's already available in the\n    caller's context.\n\n  - One use where we compute the hashmap key of an object ID. We should\n    only ever get valid, populated object IDs here, and consequently we\n    can easily adapt that function to use the hash algorithm of the\n    passed-in object ID.\n\nAdapt both sites accordingly. Safeguard us against the case where the\npassed-in object ID is _not_ properly initialized. While this case\nshouldn't ever happen, it doesn't hurt to be defensive.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f8c2cf7a93..7c73fa108b 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -133,7 +133,9 @@ void submodule_cache_free(struct submodule_cache *cache)\n static unsigned int hash_oid_string(const struct object_id *oid,\n \t\t\t\t    const char *string)\n {\n-\treturn memhash(oid->hash, the_hash_algo->rawsz) + strhash(string);\n+\tif (oid->algo == GIT_HASH_UNKNOWN)\n+\t\tBUG(\"hashing an object ID with unknown algorithm\");\n+\treturn memhash(oid->hash, hash_algos[oid->algo].rawsz) + strhash(string);\n }\n \n static void cache_put_path(struct submodule_cache *cache,\n@@ -824,7 +826,7 @@ static int gitmodules_cb(const char *var, const char *value,\n \n \tparameter.cache = repo->submodule_cache;\n \tparameter.treeish_name = NULL;\n-\tparameter.gitmodules_oid = null_oid(the_hash_algo);\n+\tparameter.gitmodules_oid = null_oid(repo->hash_algo);\n \tparameter.overwrite = 1;\n \n \treturn parse_config(var, value, ctx, &parameter);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552506","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-5-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 05/13] submodule-config: stop registering submodule sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:48Z","receivedAt":"2026-09-11T05:52:04Z","isPatch":true,"body":"When reading the \".gitmodules\" file from a blob in a repository other\nthan `the_repository`, we register that repository's object database as\nan in-memory source of `the_repository`'s object database. This call has\nits origins in d9b8b8f896 (submodule-config.c: use repo_get_oid for\nreading .gitmodules, 2019-04-16): back then, `config_with_options()` was\nnot able to read a blob from an arbitrary repository, but would always\nread it via `the_repository`. So even though the blob could be resolved\nin the submodule repository via `repo_get_oid()`, the submodule's object\ndatabase had to be registered as an in-memory source of `the_repository`\nso that the subsequent object read was able to find the blob at all.\n\nThat need went away with e3e8bf046e (submodule-config: pass repo\nupon blob config read, 2021-08-16), which taught the config machinery\nto read the blob from the repository we pass to it. The same series\nconverted the eager submodule source registration into a lazy mechanism\nthat only registers submodule sources with the object database when an\nobject lookup failed. The intent though was that we don't ever have to\nfall back to this mechanism in the first place, and to verify that this\nis the case we introduced GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB. If set,\nthen any such lazy registration would cause us to BUG.\n\nAt the beginning of this series, we still triggered this bug in t1092.\nBut now that we have converted the \"cache-tree\" subsystem to not depend\non `the_repository` anymore it also knows to properly access objects via\nthe submodule. With that change, GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\ndoes not cause any failures anymore.\n\nRemove the call to `odb_add_submodule_source_by_path()`. This removes\nthe last user of `the_repository`, so at the same time we can also get\nrid of `USE_THE_REPOSITORY_VARIABLE`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 4 ----\n 1 file changed, 4 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 7c73fa108b..37c3be377b 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_REPOSITORY_VARIABLE\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\n@@ -803,9 +802,6 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void\n \t\t} else if (repo_get_oid(repo, GITMODULES_INDEX, &oid) >= 0 ||\n \t\t\t   repo_get_oid(repo, GITMODULES_HEAD, &oid) >= 0) {\n \t\t\tconfig_source.blob = oidstr = xstrdup(oid_to_hex(&oid));\n-\t\t\tif (repo != the_repository)\n-\t\t\t\todb_add_submodule_source_by_path(the_repository->objects,\n-\t\t\t\t\t\t\t\t repo->objects->sources->path);\n \t\t} else {\n \t\t\tgoto out;\n \t\t}\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552507","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-6-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 06/13] builtin/grep: stop registering submodule ODB as source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:49Z","receivedAt":"2026-09-11T05:52:07Z","isPatch":true,"body":"Same as with the preceding commit, git-grep(1) registers each\nsubmodule's object database as an in-memory source of the main object\ndatabase before grepping it. This was introduced as an eager alternate\nregistration and converted into the lazy mechanism via 8d33c3af0b (grep:\nuse submodule-ODB-as-alternate lazy-addition, 2021-08-16).\n\nStarting with 0693806bf8 (grep: add repository to OID grep sources,\n2021-08-16), the command instead knows to pass submodule repositories to\nour workers, which means that those now use that repository to look up\nobjects, too. As a consequence, registering submodule sources as\nalternates is not required anymore.\n\nRemove the logic to register submodule sources. Unfortunately, this does\nnot allow us to get rid of the object read lock as initializing the\nsubrepository is still racy.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c | 26 ++++++--------------------\n 1 file changed, 6 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 073dfaaf45..b045f8a488 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -463,16 +463,6 @@ static int grep_submodule(struct grep_opt *opt,\n \tALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc);\n \trepos_to_free[repos_to_free_nr++] = subrepo;\n \n-\t/*\n-\t * NEEDSWORK: repo_read_gitmodules() might call\n-\t * odb_add_to_alternates_memory() via config_from_gitmodules(). This\n-\t * operation causes a race condition with concurrent object readings\n-\t * performed by the worker threads. That's why we need obj_read_lock()\n-\t * here. It should be removed once it's no longer necessary to add the\n-\t * subrepo's odbs to the in-memory alternates list.\n-\t */\n-\tobj_read_lock();\n-\n \t/*\n \t * NEEDSWORK: when reading a submodule, the sparsity settings in the\n \t * superproject are incorrectly forgotten or misused. For example:\n@@ -498,18 +488,14 @@ static int grep_submodule(struct grep_opt *opt,\n \t *\tditto.\n \t *\n \t * Note that this list is not exhaustive.\n+\t *\n+\t * NEEDSWORK: initializing the subrepository is not thread-safe,\n+\t * either, as it may cause us to race around `get_main_ref_store()`. We\n+\t * thus need to hold the object-read lock to serialize all readers with\n+\t * one another.\n \t */\n+\tobj_read_lock();\n \trepo_read_gitmodules(subrepo, 0);\n-\n-\t/*\n-\t * All code paths tested by test code no longer need submodule ODBs to\n-\t * be added as alternates, but add it to the list just in case.\n-\t * Submodule ODBs added through add_submodule_odb_by_path() will be\n-\t * lazily registered as alternates when needed (and except in an\n-\t * unexpected code interaction, it won't be needed).\n-\t */\n-\todb_add_submodule_source_by_path(the_repository->objects,\n-\t\t\t\t\t subrepo->objects->sources->path);\n \tobj_read_unlock();\n \n \tmemcpy(&subopt, opt, sizeof(subopt));\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552508","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-7-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 07/13] odb: remove infrastructure to register submodule sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:50Z","receivedAt":"2026-09-11T05:52:09Z","isPatch":true,"body":"The preceding commits have removed the last two users of\n`odb_add_submodule_source_by_path()`. The mechanism was only ever\nmeant as a transitional crutch while migrating submodule object\naccess away from \"add the submodule ODB as an alternate of\nthe_repository\" towards explicitly passing the submodule repository,\nsee a35e03dee0 (submodule: lazily add submodule ODBs as alternates,\n2021-08-16). Remove it.\n\nAs GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB is now a no-op, remove its\ndocumentation and the exports from the test suite, as well.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                                  | 36 ----------------------------------\n odb.h                                  | 14 -------------\n t/README                               |  7 -------\n t/t5526-fetch-submodules.sh            |  3 ---\n t/t5531-deep-submodule-push.sh         |  3 ---\n t/t5545-push-options.sh                |  3 ---\n t/t5572-pull-submodule.sh              |  3 ---\n t/t6437-submodule-merge.sh             |  3 ---\n t/t7418-submodule-sparse-gitmodules.sh |  3 ---\n t/t7814-grep-recurse-submodules.sh     |  3 ---\n 10 files changed, 78 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 6d5943e5ea..2f8a70a90c 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -388,12 +388,6 @@ struct odb_source *odb_find_source_or_die(struct object_database *odb, const cha\n \treturn source;\n }\n \n-void odb_add_submodule_source_by_path(struct object_database *odb,\n-\t\t\t\t      const char *path)\n-{\n-\tstring_list_insert(&odb->submodule_source_paths, path);\n-}\n-\n static void fill_alternate_refs_command(struct repository *repo,\n \t\t\t\t\tstruct child_process *cmd,\n \t\t\t\t\tconst char *repo_path)\n@@ -549,23 +543,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-static int register_all_submodule_sources(struct object_database *odb)\n-{\n-\tint ret = odb->submodule_source_paths.nr;\n-\n-\tfor (size_t i = 0; i < odb->submodule_source_paths.nr; i++)\n-\t\todb_add_to_alternates_memory(odb,\n-\t\t\t\t\t     odb->submodule_source_paths.items[i].string);\n-\tif (ret) {\n-\t\tstring_list_clear(&odb->submodule_source_paths, 0);\n-\t\ttrace2_data_intmax(\"submodule\", odb->repo,\n-\t\t\t\t   \"register_all_submodule_sources/registered\", ret);\n-\t\tif (git_env_bool(\"GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\", 0))\n-\t\t\tBUG(\"register_all_submodule_sources() called\");\n-\t}\n-\treturn ret;\n-}\n-\n static enum odb_read_status do_oid_object_info_extended(struct object_database *odb,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n \t\t\t\t\t\t\tstruct object_info *oi, unsigned flags)\n@@ -614,16 +591,6 @@ static enum odb_read_status do_oid_object_info_extended(struct object_database *\n \t\t\t}\n \t\t}\n \n-\t\t/*\n-\t\t * This might be an attempt at accessing a submodule object as\n-\t\t * if it were in main object store (having called\n-\t\t * `odb_add_submodule_source_by_path()` on that submodule's\n-\t\t * ODB). If any such ODBs exist, register them and try again.\n-\t\t */\n-\t\tif (register_all_submodule_sources(odb))\n-\t\t\t/* We added some alternates; retry */\n-\t\t\tcontinue;\n-\n \t\t/* Check if it is a missing object */\n \t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n@@ -1109,7 +1076,6 @@ struct object_database *odb_new(struct repository *repo,\n \tCALLOC_ARRAY(o, 1);\n \to->repo = repo;\n \tpthread_mutex_init(&o->replace_mutex, NULL);\n-\tstring_list_init_dup(&o->submodule_source_paths);\n \thashmap_init(&o->source_by_path, odb_source_by_path_cmp, o, 0);\n \to->source_paths_icase = -1;\n \n@@ -1166,8 +1132,6 @@ void odb_free(struct object_database *o)\n \todb_close(o);\n \todb_free_sources(o);\n \n-\tstring_list_clear(&o->submodule_source_paths, 0);\n-\n \tfree(o);\n }\n \ndiff --git a/odb.h b/odb.h\nindex 248ee9cdfa..54548efc55 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -89,12 +89,6 @@ struct object_database {\n \tunsigned long object_count;\n \tunsigned object_count_flags;\n \tunsigned object_count_valid : 1;\n-\n-\t/*\n-\t * Submodule source paths that will be added as additional sources to\n-\t * allow lookup of submodule objects via the main object database.\n-\t */\n-\tstruct string_list submodule_source_paths;\n };\n \n enum odb_new_flags {\n@@ -224,14 +218,6 @@ void odb_restore_primary_source(struct object_database *odb,\n \t\t\t\tstruct odb_source *restore_source,\n \t\t\t\tconst char *old_path);\n \n-/*\n- * Call odb_add_submodule_source_by_path() to add the submodule at the given\n- * path to a list. The object stores of all submodules in that list will be\n- * added as additional sources in the object store when looking up objects.\n- */\n-void odb_add_submodule_source_by_path(struct object_database *odb,\n-\t\t\t\t      const char *path);\n-\n /*\n  * Iterate through all alternates of the database and execute the provided\n  * callback function for each of them. Stop iterating once the callback\ndiff --git a/t/README b/t/README\nindex 9a9daaf2af..f831c5355b 100644\n--- a/t/README\n+++ b/t/README\n@@ -462,13 +462,6 @@ GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting\n to <n> and 'checkout.thresholdForParallelism' to 0, forcing the\n execution of the parallel-checkout code.\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=<boolean>, when true, makes\n-registering submodule ODBs as alternates a fatal action. Support for\n-this environment variable can be removed once the migration to\n-explicitly providing repositories when accessing submodule objects is\n-complete or needs to be abandoned for whatever reason (in which case the\n-migrated codepaths still retain their performance benefits).\n-\n GIT_TEST_REQUIRE_PREREQ=<list> allows specifying a space separated list of\n prereqs that are required to succeed. If a prereq in this list is triggered by\n a test and then fails then the whole test run will abort. This can help to make\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 7b3b7359da..37d7373b36 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -3,9 +3,6 @@\n \n test_description='Recursive \"git fetch\" for submodules'\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n pwd=$(pwd)\ndiff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh\nindex 7d239dd31f..73429ec6e3 100755\n--- a/t/t5531-deep-submodule-push.sh\n+++ b/t/t5531-deep-submodule-push.sh\n@@ -5,9 +5,6 @@ test_description='test push with submodules'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh\nindex fb13549da7..239edd7d62 100755\n--- a/t/t5545-push-options.sh\n+++ b/t/t5545-push-options.sh\n@@ -5,9 +5,6 @@ test_description='pushing to a repository using push options'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n mk_repo_pair () {\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 42d14328b6..9969a3294e 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -2,9 +2,6 @@\n \n test_description='pull can handle submodules'\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-submodule-update.sh\n \ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 107e13afbc..1546d5f773 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -5,9 +5,6 @@ test_description='merging with submodules'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n #\ndiff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh\nindex dde11ecce8..cf94e30e78 100755\n--- a/t/t7418-submodule-sparse-gitmodules.sh\n+++ b/t/t7418-submodule-sparse-gitmodules.sh\n@@ -12,9 +12,6 @@ The test setup uses a sparse checkout, however the same scenario can be set up\n also by committing .gitmodules and then just removing it from the filesystem.\n '\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh\nindex e1cf53dc9e..3d149d34c1 100755\n--- a/t/t7814-grep-recurse-submodules.sh\n+++ b/t/t7814-grep-recurse-submodules.sh\n@@ -9,9 +9,6 @@ submodules.\n TEST_CREATE_REPO_NO_TEMPLATE=1\n . ./test-lib.sh\n \n-GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1\n-export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB\n-\n test_expect_success 'setup directory structure and submodule' '\n \techo \"(1|2)d(3|4)\" >a &&\n \tmkdir b &&\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552509","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-8-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 08/13] tmp-objdir: drop unused function to register alternate","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:51Z","receivedAt":"2026-09-11T05:52:11Z","isPatch":true,"body":"The last caller of `tmp_objdir_add_as_alternate()` went away in\nbdee7b3013 (builtin/receive-pack: stage incoming objects via ODB\ntransactions, 2026-07-10) and is unused now. Remove the function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n tmp-objdir.c | 5 -----\n tmp-objdir.h | 6 ------\n 2 files changed, 11 deletions(-)\n\ndiff --git a/tmp-objdir.c b/tmp-objdir.c\nindex 0eaa79ffd7..deaaf6ba2e 100644\n--- a/tmp-objdir.c\n+++ b/tmp-objdir.c\n@@ -321,11 +321,6 @@ const char **tmp_objdir_env(const struct tmp_objdir *t)\n \treturn t->env.v;\n }\n \n-void tmp_objdir_add_as_alternate(const struct tmp_objdir *t)\n-{\n-\todb_add_to_alternates_memory(t->repo->objects, t->path.buf);\n-}\n-\n struct odb_source *tmp_objdir_replace_primary_odb(struct tmp_objdir *t,\n \t\t\t\t\t\t  int will_destroy)\n {\ndiff --git a/tmp-objdir.h b/tmp-objdir.h\nindex 81eb927413..05f0d08d10 100644\n--- a/tmp-objdir.h\n+++ b/tmp-objdir.h\n@@ -55,12 +55,6 @@ int tmp_objdir_destroy(struct tmp_objdir *);\n  */\n void tmp_objdir_discard_objects(struct tmp_objdir *);\n \n-/*\n- * Add the temporary object directory as an alternate object store in the\n- * current process.\n- */\n-void tmp_objdir_add_as_alternate(const struct tmp_objdir *);\n-\n /*\n  * Replaces the writable object store in the current process with the temporary\n  * object directory and makes the former main object store an alternate.\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552510","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-9-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 09/13] odb/packed: fix memory leaks when freeing source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:52Z","receivedAt":"2026-09-11T05:52:13Z","isPatch":true,"body":"When freeing a \"packed\" source we don't close either its packs nor its\nmulti-pack indices. This can cause memory leaks in case we create an\nad-hoc packed source. As we used to always link packed sources to the\nmain object database we never noticed this issue until now, but it's\ngoing to surface in subsequent commits where we stop linking them.\n\nPlug the memory leaks by closing the source first.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-packed.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 1d90e714e6..166e76e2d6 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -844,6 +844,7 @@ static void odb_source_packed_free(struct odb_source *source)\n \n \tchdir_notify_unregister(odb_source_packed_reparent, packed);\n \n+\todb_source_close(source);\n \tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)\n \t\tfree(e->pack);\n \tpackfile_list_clear(&packed->packs);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552511","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-10-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 10/13] builtin/multi-pack-index: refuse unknown sources with \"--object-dir=\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:53Z","receivedAt":"2026-09-11T05:52:15Z","isPatch":true,"body":"Users can tell git-multi-pack-index(1) to access multi-pack indices that\nare stored in a different object directory via the \"--object-dir=\"\noption. This allows them to for example write or verify a multi-pack\nindex other than the one located in the main object directory in case a\nrepository has alternates with multiple multi-pack indices.\n\nBut while the documentation explicitly points out that the specified\nobject directory must be an alternate of the current repository, we\nnever verify that property. Instead, starting with 017db7bb14 (midx:\nload multi-pack indices via their source, 2025-08-11), we now construct\nan ad-hoc source and link it to the main object directory.\n\nBesides contradicting the documentation, it's dubious that this really\nought to work in the first place: creating a multi-pack index (and\npotentially a bitmap) for a completely foreign object directory is of\nquestionable value, as bitmap commit selection operates on the invoking\nrepository's refs. Furthermore, this is the only remaining caller\noutside of our test helpers that constructs an ad-hoc source and links\nit to the database, and we want to get rid of this mechanism as part of\nthis series.\n\nStop constructing the ad-hoc source and instead refuse the operation.\nWhile this results in a change in behaviour, this restriction has been\ndocumented as such ever since f57a739691 (midx: avoid opening multiple\nMIDXs when writing, 2021-09-01).\n\nNote that this change requires us to adapt one test chain in t5319, as\nit creates an object directory that is not connected to any repository\nand then uses it via \"--object-dir=\". The setup itself already documents\nthis and does the necessary gymnastics to link the object directory to a\ntemporary repository, but subsequent tests don't. Adapt those tests to\nretain and reuse the temporary repository.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/multi-pack-index.c  | 3 ++-\n t/t5319-multi-pack-index.sh | 9 ++++-----\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 6e73c85cde..753bd53a70 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -90,7 +90,8 @@ static struct odb_source_files *handle_object_dir_option(struct repository *repo\n {\n \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n \tif (!source)\n-\t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n+\t\tdie(_(\"object directory is not an alternate of the current repository: '%s'\"),\n+\t\t    opts.object_dir);\n \treturn odb_source_files_downcast(source);\n }\n \ndiff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh\nindex 68143cb5b7..00e90f163f 100755\n--- a/t/t5319-multi-pack-index.sh\n+++ b/t/t5319-multi-pack-index.sh\n@@ -698,10 +698,9 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '\n \tcorrupt_data $idx64 $(test_oid idxoff) \"\\02\" &&\n \t# objects64 is not a real repository, but can serve as an alternate\n \t# anyway so we can write a MIDX into it\n-\tgit init repo &&\n-\ttest_when_finished \"rm -fr repo\" &&\n+\tgit init repo64 &&\n \t(\n-\t\tcd repo &&\n+\t\tcd repo64 &&\n \t\t( cd ../objects64 && pwd ) >.git/objects/info/alternates &&\n \t\tmidx64=$(git multi-pack-index --object-dir=../objects64 write)\n \t) &&\n@@ -709,7 +708,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '\n '\n \n test_expect_success 'verify multi-pack-index with 64-bit offsets' '\n-\tgit multi-pack-index verify --object-dir=objects64\n+\tgit -C repo64 multi-pack-index verify --object-dir=../objects64\n '\n \n NUM_OBJECTS=63\n@@ -721,7 +720,7 @@ MIDX_BYTE_LARGE_OFFSET=$(($MIDX_OFFSET_LARGE_OFFSETS + 3))\n \n test_expect_success 'verify incorrect 64-bit offset' '\n \tcorrupt_midx_and_verify $MIDX_BYTE_LARGE_OFFSET \"\\07\" objects64 \\\n-\t\t\"incorrect object offset\"\n+\t\t\"incorrect object offset\" \"git -C repo64 multi-pack-index verify --object-dir=../objects64\"\n '\n \n test_expect_success 'setup expire tests' '\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552512","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-11-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 11/13] t/helper: adapt read-midx to not link ad-hoc source anymore","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:54Z","receivedAt":"2026-09-11T05:52:18Z","isPatch":true,"body":"Same as in the preceding commit, refactor the setup of ad-hoc object\ndatabase sources when accessing a multi-pack index in an arbitrary\nlocation to not link the newly created source into the main object\ndatabase anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/helper/test-read-midx.c | 43 +++++++++++++++++++++++++++++++------------\n 1 file changed, 31 insertions(+), 12 deletions(-)\n\ndiff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c\nindex 27a05da957..1f7a1927e4 100644\n--- a/t/helper/test-read-midx.c\n+++ b/t/helper/test-read-midx.c\n@@ -5,34 +5,42 @@\n #include \"midx.h\"\n #include \"repository.h\"\n #include \"odb.h\"\n+#include \"odb/source-packed.h\"\n #include \"pack-bitmap.h\"\n #include \"packfile.h\"\n #include \"setup.h\"\n #include \"gettext.h\"\n #include \"pack-revindex.h\"\n \n-static struct multi_pack_index *setup_midx(const char *object_dir)\n+static struct multi_pack_index *setup_midx(const char *object_dir,\n+\t\t\t\t\t   struct odb_source_packed **out)\n {\n-\tstruct odb_source_files *files;\n+\tstruct odb_source_packed *packed;\n \tstruct odb_source *source;\n+\n \tsetup_git_directory(the_repository);\n+\n \tsource = odb_find_source(the_repository->objects, object_dir);\n-\tif (!source)\n-\t\tsource = odb_add_to_alternates_memory(the_repository->objects,\n-\t\t\t\t\t\t      object_dir);\n-\tfiles = odb_source_files_downcast(source);\n+\tif (source) {\n+\t\tpacked = odb_source_files_downcast(source)->packed;\n+\t} else {\n+\t\tpacked = odb_source_packed_new(the_repository->objects,\n+\t\t\t\t\t       object_dir, false);\n+\t\t*out = packed;\n+\t}\n \n-\treturn load_multi_pack_index(files->packed);\n+\treturn load_multi_pack_index(packed);\n }\n \n static int read_midx_file(const char *object_dir, const char *checksum,\n \t\t\t  int show_objects)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tuint32_t i;\n \tstruct multi_pack_index *m, *tip;\n \tint ret = 0;\n \n-\tm = tip = setup_midx(object_dir);\n+\tm = tip = setup_midx(object_dir, &packed);\n \n \tif (!m)\n \t\treturn 1;\n@@ -91,29 +99,35 @@ static int read_midx_file(const char *object_dir, const char *checksum,\n \n out:\n \tclose_midx(tip);\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \n \treturn ret;\n }\n \n static int read_midx_checksum(const char *object_dir)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tstruct multi_pack_index *m;\n \n-\tm = setup_midx(object_dir);\n+\tm = setup_midx(object_dir, &packed);\n \tif (!m)\n \t\treturn 1;\n \tprintf(\"%s\\n\", midx_get_checksum_hex(m));\n \n \tclose_midx(m);\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \treturn 0;\n }\n \n static int read_midx_preferred_pack(const char *object_dir)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tstruct multi_pack_index *midx = NULL;\n \tuint32_t preferred_pack;\n \n-\tmidx = setup_midx(object_dir);\n+\tmidx = setup_midx(object_dir, &packed);\n \tif (!midx)\n \t\treturn 1;\n \n@@ -124,17 +138,21 @@ static int read_midx_preferred_pack(const char *object_dir)\n \t}\n \n \tprintf(\"%s\\n\", midx->pack_names[preferred_pack]);\n+\n \tclose_midx(midx);\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \treturn 0;\n }\n \n static int read_midx_bitmapped_packs(const char *object_dir)\n {\n+\tstruct odb_source_packed *packed = NULL;\n \tstruct multi_pack_index *midx = NULL;\n \tstruct bitmapped_pack pack;\n \tuint32_t i;\n \n-\tmidx = setup_midx(object_dir);\n+\tmidx = setup_midx(object_dir, &packed);\n \tif (!midx)\n \t\treturn 1;\n \n@@ -150,7 +168,8 @@ static int read_midx_bitmapped_packs(const char *object_dir)\n \t}\n \n \tclose_midx(midx);\n-\n+\tif (packed)\n+\t\todb_source_free(&packed->base);\n \treturn 0;\n }\n \n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552513","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-12-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 12/13] t/helper: stop registering alternates in \"ref-store\" command","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:55Z","receivedAt":"2026-09-11T05:52:20Z","isPatch":true,"body":"When using the \"ref-store\" command we support access to multiple\ndifferent reference stores. As part of that we allow the caller to\nexplicitly exercise stores of a submodule. This allows us to verify\nlow-level behaviour of submodule stores, which is exercised in t1406.\n\nWhen doing so we also link the submodule's object database into the main\nobject database. The intent of this is that it allows us to access\nobjects of the submodule, too. But that functionality is not even\nneeded anymore: when creating a submodule reference store, we will first\ninitialize the submodule repository and then initialize the store with\nthat repository. And as the reference subsystem doesn't depend on\n`the_repository` anymore all subsequent object lookups performed by the\nreference store will be routed to the submodule repository.\n\nIt is thus not needed anymore to register the submodule object store\nwith the main object database. Remove the call.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/helper/test-ref-store.c | 8 --------\n 1 file changed, 8 deletions(-)\n\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 5a9a3053d9..db58f00589 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -74,14 +74,6 @@ static const char **get_store(const char **argv, struct ref_store **refs)\n \t} else if (!strcmp(argv[0], \"main\")) {\n \t\t*refs = get_main_ref_store(the_repository);\n \t} else if (skip_prefix(argv[0], \"submodule:\", &gitdir)) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\n-\t\tif (!repo_submodule_path_append(the_repository,\n-\t\t\t\t\t\t&sb, gitdir, \"objects/\"))\n-\t\t\tdie(\"computing submodule path failed\");\n-\t\todb_add_to_alternates_memory(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n-\n \t\t*refs = repo_get_submodule_ref_store(the_repository, gitdir);\n \t} else if (skip_prefix(argv[0], \"worktree:\", &gitdir)) {\n \t\tstruct worktree **p, **worktrees = get_worktrees(the_repository);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552514","messageId":"20260911-pks-odb-registering-in-memory-sources-v3-13-447e6882ed14@pks.im","threadId":"66246","inReplyTo":"20260911-pks-odb-registering-in-memory-sources-v3-0-447e6882ed14@pks.im","subject":"[PATCH v3 13/13] odb: remove the ability to link sources ad-hoc","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:51:56Z","receivedAt":"2026-09-11T05:52:23Z","isPatch":true,"body":"Over the course of this patch series we have adapted all callers of\n`odb_add_to_alternates_memory()` to not do so anymore. Remove the\nfunction.\n\nThis series of refactorings doesn't only simplify our code base. More\nimportantly, with those changes in place we can now unconditionally\nassume that the list of sources linked to the object database only\nconsists of the primary source and its alternates. This serves as the\nfoundation to eventually move handling of alternates into the \"files\"\nbackend itself.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c | 6 ------\n odb.h | 8 --------\n 2 files changed, 14 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 2f8a70a90c..5fe081496f 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -247,12 +247,6 @@ void odb_add_to_alternates_file(struct object_database *odb,\n \todb_add_alternate_recursively(odb, dir, 0);\n }\n \n-struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n-\t\t\t\t\t\tconst char *dir)\n-{\n-\treturn odb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,\n \t\t\t\t\t\t    const char *dir, int will_destroy,\n \t\t\t\t\t\t    struct odb_source **prev_source)\ndiff --git a/odb.h b/odb.h\nindex 54548efc55..9025239df5 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -258,14 +258,6 @@ int odb_has_alternates(struct object_database *odb);\n void odb_add_to_alternates_file(struct object_database *odb,\n \t\t\t\tconst char *dir);\n \n-/*\n- * Add the directory to the in-memory list of alternate sources (along with any\n- * recursive alternates it points to), but do not modify the on-disk alternates\n- * file.\n- */\n-struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n-\t\t\t\t\t\tconst char *dir);\n-\n /*\n  * Read an object from the database. Returns the object data and assigns object\n  * type and size to the `type` and `size` pointers, if these pointers are\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"}]}