From b4dedbc0615bddbad6a5bd18345d9da766d95dfa Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:44 +0200 Subject: [PATCH 01/13] cache-tree: drop `the_repository` in `cache_tree_fully_valid()` The function `cache_tree_fully_valid()` verifies whether the cache tree owned by the index is valid or not. As part of that, the function checks whether the objects referenced by the cache all exist. But because the function has no repository available, it is using the object database of `the_repository` instead. We could of course adapt callers to pass in a repository as parameter explicitly to get rid of this implicit dependency on global state. But all of them pass the cache tree owned by a `struct index_state`, and that structure already has a reference to its owning repository. So instead, adapt the function to accept a `struct index_state`, which ensures that callers will implicitly always pass the correct repository. Adapt callers accordingly. Suggested-by: Junio C Hamano Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/checkout.c | 2 +- builtin/commit.c | 2 +- cache-tree.c | 17 ++++++++++++----- cache-tree.h | 2 +- sequencer.c | 2 +- sparse-index.c | 2 +- unpack-trees.c | 2 +- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index 55e3a89a85..505d3f7bf3 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -921,7 +921,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (!cache_tree_fully_valid(the_repository->index->cache_tree)) + if (!cache_tree_fully_valid(the_repository->index)) cache_tree_update(the_repository->index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) diff --git a/builtin/commit.c b/builtin/commit.c index 28f6174503..840b6b4083 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -484,7 +484,7 @@ static const char *prepare_index(const char **argv, const char *prefix, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); if (the_repository->index->cache_changed - || !cache_tree_fully_valid(the_repository->index->cache_tree)) + || !cache_tree_fully_valid(the_repository->index)) cache_tree_update(the_repository->index, WRITE_TREE_SILENT); if (write_locked_index(the_repository->index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) diff --git a/cache-tree.c b/cache-tree.c index a220372a42..6103b3fcb3 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -275,22 +275,29 @@ static void discard_unused_subtrees(struct cache_tree *it) } } -int cache_tree_fully_valid(struct cache_tree *it) +static int cache_tree_fully_valid_recursive(struct object_database *odb, + struct cache_tree *it) { int i; if (!it) return 0; if (it->entry_count < 0 || - !odb_has_object(the_repository->objects, &it->oid, + !odb_has_object(odb, &it->oid, ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR)) return 0; for (i = 0; i < it->subtree_nr; i++) { - if (!cache_tree_fully_valid(it->down[i]->cache_tree)) + if (!cache_tree_fully_valid_recursive(odb, it->down[i]->cache_tree)) return 0; } return 1; } +int cache_tree_fully_valid(struct index_state *istate) +{ + return cache_tree_fully_valid_recursive(istate->repo->objects, + istate->cache_tree); +} + static int must_check_existence(const struct cache_entry *ce) { return !(repo_has_promisor_remote(the_repository) && ce_skip_worktree(ce)); @@ -775,7 +782,7 @@ struct tree *write_in_core_index_as_tree(struct repository *repo, int was_valid, ret; was_valid = index_state->cache_tree && - cache_tree_fully_valid(index_state->cache_tree); + cache_tree_fully_valid(index_state); ret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL); if (ret == WRITE_TREE_UNMERGED_INDEX) { @@ -811,7 +818,7 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state, was_valid = !(flags & WRITE_TREE_IGNORE_CACHE_TREE) && index_state->cache_tree && - cache_tree_fully_valid(index_state->cache_tree); + cache_tree_fully_valid(index_state); ret = write_index_as_tree_internal(oid, index_state, was_valid, flags, prefix); diff --git a/cache-tree.h b/cache-tree.h index f8bddae523..4b3f60d6db 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -31,7 +31,7 @@ int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen) void cache_tree_write(struct strbuf *, struct cache_tree *root); struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); -int cache_tree_fully_valid(struct cache_tree *); +int cache_tree_fully_valid(struct index_state *); int cache_tree_update(struct index_state *, int); int cache_tree_verify(struct repository *, struct index_state *); diff --git a/sequencer.c b/sequencer.c index 65afd100d9..11a95c031b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -814,7 +814,7 @@ static int do_recursive_merge(struct repository *r, static struct object_id *get_cache_tree_oid(struct index_state *istate) { - if (!cache_tree_fully_valid(istate->cache_tree)) + if (!cache_tree_fully_valid(istate)) if (cache_tree_update(istate, 0)) { error(_("unable to update cache tree")); return NULL; diff --git a/sparse-index.c b/sparse-index.c index c1fa231a89..3d77dadae5 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -228,7 +228,7 @@ int convert_to_sparse(struct index_state *istate, int flags) if (index_has_unmerged_entries(istate)) return 0; - if (!cache_tree_fully_valid(istate->cache_tree)) { + if (!cache_tree_fully_valid(istate)) { /* Clear and recompute the cache-tree */ cache_tree_free(&istate->cache_tree); diff --git a/unpack-trees.c b/unpack-trees.c index 154d6d40a1..f6bb1e6d2b 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -2086,7 +2086,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } if (!o->skip_cache_tree_update && - !cache_tree_fully_valid(o->internal.result.cache_tree)) + !cache_tree_fully_valid(&o->internal.result)) cache_tree_update(&o->internal.result, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); From f9de4b7ff70a70d8e5c4abca418756e959aaea85 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:45 +0200 Subject: [PATCH 02/13] cache-tree: remove dependency on `the_repository` The "cache-tree" subsystem still depends on `the_repository`. Adapt it to instead use repositories provided via the context, either as a new parameter or the one passed in via `struct index_state`. Besides getting rid of `the_repository`, this also removes the last dependency on registering submodule sources with the main object database. When reading gitmodules from a submodule's index we implicitly read that object via `the_repository`'s object database, which is of course wrong. This works though because we would then register the submodule's object database with the main object database, but a later patch is going to get rid of that mechanism. You can verify that we indeed no longer depend on this mechanism by running tests with `GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=true`. Without this patch we fail in t1092, with this patch we never register submodule object databases anymore. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- cache-tree.c | 78 ++++++++++++++++++++++++++----------------------- cache-tree.h | 5 ++-- read-cache-ll.h | 5 ++-- read-cache.c | 9 +++--- unpack-trees.c | 7 +++-- 5 files changed, 57 insertions(+), 47 deletions(-) diff --git a/cache-tree.c b/cache-tree.c index 6103b3fcb3..b8cbb5da22 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -298,12 +297,14 @@ int cache_tree_fully_valid(struct index_state *istate) istate->cache_tree); } -static int must_check_existence(const struct cache_entry *ce) +static int must_check_existence(const struct cache_entry *ce, void *cb_data) { - return !(repo_has_promisor_remote(the_repository) && ce_skip_worktree(ce)); + struct repository *repo = cb_data; + return !(repo_has_promisor_remote(repo) && ce_skip_worktree(ce)); } -static int update_one(struct cache_tree *it, +static int update_one(struct repository *repo, + struct cache_tree *it, struct cache_entry **cache, int entries, const char *base, @@ -341,7 +342,7 @@ static int update_one(struct cache_tree *it, } if (0 <= it->entry_count && - odb_has_object(the_repository->objects, &it->oid, + odb_has_object(repo->objects, &it->oid, ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR)) return it->entry_count; @@ -382,7 +383,8 @@ static int update_one(struct cache_tree *it, sub = find_subtree(it, path + baselen, sublen, 1); if (!sub->cache_tree) sub->cache_tree = cache_tree(); - subcnt = update_one(sub->cache_tree, + subcnt = update_one(repo, + sub->cache_tree, cache + i, entries - i, path, baselen + sublen + 1, @@ -446,10 +448,10 @@ static int update_one(struct cache_tree *it, } ce_missing_ok = mode == S_IFGITLINK || missing_ok || - !must_check_existence(ce); + !must_check_existence(ce, repo); if (is_null_oid(oid) || (!ce_missing_ok && - !odb_has_object(the_repository->objects, oid, + !odb_has_object(repo->objects, oid, ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR))) { strbuf_release(&buffer); if (expected_missing) @@ -481,12 +483,12 @@ static int update_one(struct cache_tree *it, /* * "sub" can be an empty tree if all subentries are i-t-a. */ - if (contains_ita && is_empty_tree_oid(oid, the_repository->hash_algo)) + if (contains_ita && is_empty_tree_oid(oid, repo->hash_algo)) continue; strbuf_grow(&buffer, entlen + 100); strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0'); - strbuf_add(&buffer, oid->hash, the_hash_algo->rawsz); + strbuf_add(&buffer, oid->hash, repo->hash_algo->rawsz); #if DEBUG_CACHE_TREE fprintf(stderr, "cache-tree update-one %o %.*s\n", @@ -496,16 +498,16 @@ static int update_one(struct cache_tree *it, if (repair) { struct object_id oid; - hash_object_file(the_hash_algo, buffer.buf, buffer.len, + hash_object_file(repo->hash_algo, buffer.buf, buffer.len, OBJ_TREE, &oid); - if (odb_has_object(the_repository->objects, &oid, ODB_HAS_OBJECT_RECHECK_PACKED)) + if (odb_has_object(repo->objects, &oid, ODB_HAS_OBJECT_RECHECK_PACKED)) oidcpy(&it->oid, &oid); else to_invalidate = 1; } else if (dryrun) { - hash_object_file(the_hash_algo, buffer.buf, buffer.len, + hash_object_file(repo->hash_algo, buffer.buf, buffer.len, OBJ_TREE, &it->oid); - } else if (odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len, OBJ_TREE, + } else if (odb_write_object_ext(repo->objects, buffer.buf, buffer.len, OBJ_TREE, &it->oid, NULL, flags & WRITE_TREE_SILENT ? ODB_WRITE_OBJECT_SILENT : 0)) { strbuf_release(&buffer); return -1; @@ -523,7 +525,7 @@ static int update_one(struct cache_tree *it, int cache_tree_update(struct index_state *istate, int flags) { - int inflight = !!the_repository->objects->transaction; + int inflight = !!istate->repo->objects->transaction; struct odb_transaction *transaction; int skip, i; @@ -535,14 +537,14 @@ int cache_tree_update(struct index_state *istate, int flags) if (!istate->cache_tree) istate->cache_tree = cache_tree(); - if (!(flags & WRITE_TREE_MISSING_OK) && repo_has_promisor_remote(the_repository)) - prefetch_cache_entries(istate, must_check_existence); + if (!(flags & WRITE_TREE_MISSING_OK) && repo_has_promisor_remote(istate->repo)) + prefetch_cache_entries(istate, must_check_existence, istate->repo); trace_performance_enter(); trace2_region_enter("cache_tree", "update", istate->repo); if (!inflight) - odb_transaction_begin_or_die(the_repository->objects, &transaction, 0); - i = update_one(istate->cache_tree, istate->cache, istate->cache_nr, + odb_transaction_begin_or_die(istate->repo->objects, &transaction, 0); + i = update_one(istate->repo, istate->cache_tree, istate->cache, istate->cache_nr, "", 0, &skip, flags); if (!inflight) odb_transaction_commit_and_finalize_or_die(transaction); @@ -554,7 +556,8 @@ int cache_tree_update(struct index_state *istate, int flags) return 0; } -static void write_one(struct strbuf *buffer, struct cache_tree *it, +static void write_one(struct repository *repo, + struct strbuf *buffer, struct cache_tree *it, const char *path, int pathlen) { int i; @@ -580,7 +583,7 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it, #endif if (0 <= it->entry_count) { - strbuf_add(buffer, it->oid.hash, the_hash_algo->rawsz); + strbuf_add(buffer, it->oid.hash, repo->hash_algo->rawsz); } for (i = 0; i < it->subtree_nr; i++) { struct cache_tree_sub *down = it->down[i]; @@ -590,15 +593,16 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it, prev->name, prev->namelen) <= 0) die("fatal - unsorted cache subtree"); } - write_one(buffer, down->cache_tree, down->name, down->namelen); + write_one(repo, buffer, down->cache_tree, down->name, down->namelen); } } -void cache_tree_write(struct strbuf *sb, struct cache_tree *root) +void cache_tree_write(struct repository *repo, + struct strbuf *sb, struct cache_tree *root) { - trace2_region_enter("cache_tree", "write", the_repository); - write_one(sb, root, "", 0); - trace2_region_leave("cache_tree", "write", the_repository); + trace2_region_enter("cache_tree", "write", repo); + write_one(repo, sb, root, "", 0); + trace2_region_leave("cache_tree", "write", repo); } static int parse_int(const char **ptr, unsigned long *len_p, int *out) @@ -632,13 +636,14 @@ static int parse_int(const char **ptr, unsigned long *len_p, int *out) return 0; } -static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) +static struct cache_tree *read_one(struct repository *repo, + const char **buffer, unsigned long *size_p) { const char *buf = *buffer; unsigned long size = *size_p; struct cache_tree *it; int i, subtree_nr; - const unsigned rawsz = the_hash_algo->rawsz; + const unsigned rawsz = repo->hash_algo->rawsz; it = NULL; /* skip name, but make sure name exists */ @@ -665,7 +670,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) if (size < rawsz) goto free_return; oidread(&it->oid, (const unsigned char *)buf, - the_repository->hash_algo); + repo->hash_algo); buf += rawsz; size -= rawsz; } @@ -693,7 +698,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) struct cache_tree_sub *subtree; const char *name = buf; - sub = read_one(&buf, &size); + sub = read_one(repo, &buf, &size); if (!sub) goto free_return; subtree = cache_tree_sub(it, name); @@ -710,16 +715,17 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) return NULL; } -struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) +struct cache_tree *cache_tree_read(struct repository *repo, + const char *buffer, unsigned long size) { struct cache_tree *result; if (buffer[0]) return NULL; /* not the whole tree */ - trace2_region_enter("cache_tree", "read", the_repository); - result = read_one(&buffer, &size); - trace2_region_leave("cache_tree", "read", the_repository); + trace2_region_enter("cache_tree", "read", repo); + result = read_one(repo, &buffer, &size); + trace2_region_leave("cache_tree", "read", repo); return result; } @@ -810,7 +816,7 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state, hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR); entries = read_index_from(index_state, index_path, - repo_get_git_dir(the_repository)); + repo_get_git_dir(index_state->repo)); if (entries < 0) { ret = WRITE_TREE_UNREADABLE_INDEX; goto out; @@ -866,7 +872,7 @@ static void prime_cache_tree_rec(struct repository *r, struct cache_tree_sub *sub; struct tree *subtree = lookup_tree(r, &entry.oid); - if (repo_parse_tree(the_repository, subtree) < 0) + if (repo_parse_tree(r, subtree) < 0) exit(128); sub = cache_tree_sub(it, entry.path); sub->cache_tree = cache_tree(); diff --git a/cache-tree.h b/cache-tree.h index 4b3f60d6db..7a2177de83 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -28,8 +28,9 @@ struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen); -void cache_tree_write(struct strbuf *, struct cache_tree *root); -struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); +void cache_tree_write(struct repository *repo, struct strbuf *, struct cache_tree *root); +struct cache_tree *cache_tree_read(struct repository *repo, + const char *buffer, unsigned long size); int cache_tree_fully_valid(struct index_state *); int cache_tree_update(struct index_state *, int); diff --git a/read-cache-ll.h b/read-cache-ll.h index 8eb266cfd1..066dd8bc3b 100644 --- a/read-cache-ll.h +++ b/read-cache-ll.h @@ -269,9 +269,10 @@ void validate_cache_entries(const struct index_state *istate); * the given predicate. This function should only be called if * repo_has_promisor_remote() returns true. */ -typedef int (*must_prefetch_predicate)(const struct cache_entry *); +typedef int (*must_prefetch_predicate)(const struct cache_entry *, void *cb_data); void prefetch_cache_entries(const struct index_state *istate, - must_prefetch_predicate must_prefetch); + must_prefetch_predicate must_prefetch, + void *cb_data); /* Initialize and use the cache information */ struct lock_file; diff --git a/read-cache.c b/read-cache.c index 8044ff820b..e40f290bb3 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1748,7 +1748,7 @@ static int read_index_extension(struct index_state *istate, { switch (CACHE_EXT(ext)) { case CACHE_EXT_TREE: - istate->cache_tree = cache_tree_read(data, sz); + istate->cache_tree = cache_tree_read(istate->repo, data, sz); break; case CACHE_EXT_RESOLVE_UNDO: istate->resolve_undo = resolve_undo_read(data, sz, the_hash_algo); @@ -3012,7 +3012,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, !drop_cache_tree && istate->cache_tree) { strbuf_reset(&sb); - cache_tree_write(&sb, istate->cache_tree); + cache_tree_write(istate->repo, &sb, istate->cache_tree); err = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0; hashwrite(f, sb.buf, sb.len); if (err) { @@ -3733,7 +3733,8 @@ static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_ta } void prefetch_cache_entries(const struct index_state *istate, - must_prefetch_predicate must_prefetch) + must_prefetch_predicate must_prefetch, + void *cb_data) { int i; struct oid_array to_fetch = OID_ARRAY_INIT; @@ -3741,7 +3742,7 @@ void prefetch_cache_entries(const struct index_state *istate, for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; - if (S_ISGITLINK(ce->ce_mode) || !must_prefetch(ce)) + if (S_ISGITLINK(ce->ce_mode) || !must_prefetch(ce, cb_data)) continue; if (!odb_read_object_info_extended(the_repository->objects, &ce->oid, NULL, diff --git a/unpack-trees.c b/unpack-trees.c index f6bb1e6d2b..1802809ad3 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -416,7 +416,8 @@ static void report_collided_checkout(struct index_state *index) string_list_clear(&list, 0); } -static int must_checkout(const struct cache_entry *ce) +static int must_checkout(const struct cache_entry *ce, + void *cb_data UNUSED) { return ce->ce_flags & CE_UPDATE; } @@ -477,7 +478,7 @@ static int check_updates(struct unpack_trees_options *o, * Prefetch the objects that are to be checked out in the loop * below. */ - prefetch_cache_entries(index, must_checkout); + prefetch_cache_entries(index, must_checkout, NULL); get_parallel_checkout_configs(&pc_workers, &pc_threshold); @@ -487,7 +488,7 @@ static int check_updates(struct unpack_trees_options *o, for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; - if (must_checkout(ce)) { + if (must_checkout(ce, NULL)) { size_t last_pc_queue_size = pc_queue_size(); if (ce->ce_flags & CE_WT_REMOVE) From 8751a0ffc30dc91233f968d064fe913b3eeba885 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:46 +0200 Subject: [PATCH 03/13] submodule-config: remove uses of `the_repository` Several functions in the submodule-config subsystem implicitly depend on `the_repository`. Refactor these to take a `struct repository` as parameter and adapt callers accordingly. Note that as usual with these refactorings, callers simply pass `the_repository` even if they already have a different repository available in the calling context. This simplifies the migration and ensures that we don't have a change in behaviour. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/fetch.c | 2 +- builtin/grep.c | 2 +- builtin/submodule--helper.c | 8 +++--- submodule-config.c | 49 +++++++++++++++++++++---------------- submodule-config.h | 12 ++++++--- submodule.c | 2 +- t/helper/test-submodule.c | 4 +-- 7 files changed, 45 insertions(+), 34 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index ab7db2be06..533fdfe7d8 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -2681,7 +2681,7 @@ int cmd_fetch(int argc, int *rs = config.recurse_submodules == RECURSE_SUBMODULES_DEFAULT ? &config.recurse_submodules : NULL; - fetch_config_from_gitmodules(sfjc, rs); + fetch_config_from_gitmodules(the_repository, sfjc, rs); } diff --git a/builtin/grep.c b/builtin/grep.c index d3d86abe01..073dfaaf45 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -897,7 +897,7 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, if (recurse_submodules) { submodule_free(opt->repo); obj_read_lock(); - gitmodules_config_oid(&real_obj->oid); + gitmodules_config_oid(the_repository, &real_obj->oid); obj_read_unlock(); } if (grep_object(opt, pathspec, real_obj, list->objects[i].name, diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index e7cd3225fa..aaaa963fd8 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -3041,7 +3041,7 @@ static int module_update(int argc, const char **argv, const char *prefix, NULL }; - update_clone_config_from_gitmodules(&opt.max_jobs); + update_clone_config_from_gitmodules(the_repository, &opt.max_jobs); repo_config(the_repository, git_update_clone_config, &opt.max_jobs); argc = parse_options(argc, argv, prefix, module_update_options, @@ -3255,7 +3255,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix, path); config_name = xstrfmt("submodule.%s.url", sub->name); - ret = config_set_in_gitmodules_file_gently(config_name, newurl); + ret = config_set_in_gitmodules_file_gently(the_repository, config_name, newurl); if (!ret) { repo_read_gitmodules(the_repository, 0); @@ -3311,7 +3311,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix, path); config_name = xstrfmt("submodule.%s.branch", sub->name); - ret = config_set_in_gitmodules_file_gently(config_name, opt_branch); + ret = config_set_in_gitmodules_file_gently(the_repository, config_name, opt_branch); free(config_name); return !!ret; @@ -3510,7 +3510,7 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con die(_("please make sure that the .gitmodules file is in the working tree")); key = xstrfmt("submodule.%s.%s", name, var); - ret = config_set_in_gitmodules_file_gently(key, value); + ret = config_set_in_gitmodules_file_gently(the_repository, key, value); free(key); return ret; diff --git a/submodule-config.c b/submodule-config.c index f75997402a..f8c2cf7a93 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -667,19 +667,20 @@ static int parse_config(const char *var, const char *value, return ret; } -static int gitmodule_oid_from_commit(const struct object_id *treeish_name, +static int gitmodule_oid_from_commit(struct repository *repo, + const struct object_id *treeish_name, struct object_id *gitmodules_oid, struct strbuf *rev) { int ret = 0; if (is_null_oid(treeish_name)) { - oidclr(gitmodules_oid, the_repository->hash_algo); + oidclr(gitmodules_oid, repo->hash_algo); return 1; } strbuf_addf(rev, "%s:.gitmodules", oid_to_hex(treeish_name)); - if (repo_get_oid(the_repository, rev->buf, gitmodules_oid) >= 0) + if (repo_get_oid(repo, rev->buf, gitmodules_oid) >= 0) ret = 1; return ret; @@ -689,9 +690,11 @@ static int gitmodule_oid_from_commit(const struct object_id *treeish_name, * (key) with on-demand reading of the appropriate .gitmodules from * revisions. */ -static const struct submodule *config_from(struct submodule_cache *cache, - const struct object_id *treeish_name, const char *key, - enum lookup_type lookup_type) +static const struct submodule *config_from(struct repository *repo, + struct submodule_cache *cache, + const struct object_id *treeish_name, + const char *key, + enum lookup_type lookup_type) { struct strbuf rev = STRBUF_INIT; size_t config_size; @@ -718,7 +721,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, return entry->config; } - if (!gitmodule_oid_from_commit(treeish_name, &oid, &rev)) + if (!gitmodule_oid_from_commit(repo, treeish_name, &oid, &rev)) goto out; switch (lookup_type) { @@ -732,7 +735,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, if (submodule) goto out; - config = odb_read_object(the_repository->objects, &oid, + config = odb_read_object(repo->objects, &oid, &type, &config_size); if (!config || type != OBJ_BLOB) goto out; @@ -843,21 +846,22 @@ void repo_read_gitmodules(struct repository *repo, int skip_if_read) repo->submodule_cache->gitmodules_read = 1; } -void gitmodules_config_oid(const struct object_id *commit_oid) +void gitmodules_config_oid(struct repository *repo, + const struct object_id *commit_oid) { struct strbuf rev = STRBUF_INIT; struct object_id oid; - submodule_cache_check_init(the_repository); + submodule_cache_check_init(repo); - if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) { + if (gitmodule_oid_from_commit(repo, commit_oid, &oid, &rev)) { git_config_from_blob_oid(gitmodules_cb, rev.buf, - the_repository, &oid, the_repository, + repo, &oid, repo, CONFIG_SCOPE_UNKNOWN); } strbuf_release(&rev); - the_repository->submodule_cache->gitmodules_read = 1; + repo->submodule_cache->gitmodules_read = 1; } const struct submodule *submodule_from_name(struct repository *r, @@ -865,7 +869,7 @@ const struct submodule *submodule_from_name(struct repository *r, const char *name) { repo_read_gitmodules(r, 1); - return config_from(r->submodule_cache, treeish_name, name, lookup_name); + return config_from(r, r->submodule_cache, treeish_name, name, lookup_name); } const struct submodule *submodule_from_path(struct repository *r, @@ -873,7 +877,7 @@ const struct submodule *submodule_from_path(struct repository *r, const char *path) { repo_read_gitmodules(r, 1); - return config_from(r->submodule_cache, treeish_name, path, lookup_path); + return config_from(r, r->submodule_cache, treeish_name, path, lookup_path); } /** @@ -980,11 +984,12 @@ int print_config_from_gitmodules(struct repository *repo, const char *key) return 0; } -int config_set_in_gitmodules_file_gently(const char *key, const char *value) +int config_set_in_gitmodules_file_gently(struct repository *repo, + const char *key, const char *value) { int ret; - ret = repo_config_set_in_file_gently(the_repository, GITMODULES_FILE, key, NULL, value); + ret = repo_config_set_in_file_gently(repo, GITMODULES_FILE, key, NULL, value); if (ret < 0) /* Maybe the user already did that, don't error out here */ warning(_("Could not update .gitmodules entry %s"), key); @@ -1017,13 +1022,15 @@ static int gitmodules_fetch_config(const char *var, const char *value, return 0; } -void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules) +void fetch_config_from_gitmodules(struct repository *repo, + int *max_children, + int *recurse_submodules) { struct fetch_config config = { .max_children = max_children, .recurse_submodules = recurse_submodules }; - config_from_gitmodules(gitmodules_fetch_config, the_repository, &config); + config_from_gitmodules(gitmodules_fetch_config, repo, &config); } static int gitmodules_update_clone_config(const char *var, const char *value, @@ -1036,7 +1043,7 @@ static int gitmodules_update_clone_config(const char *var, const char *value, return 0; } -void update_clone_config_from_gitmodules(int *max_jobs) +void update_clone_config_from_gitmodules(struct repository *repo, int *max_jobs) { - config_from_gitmodules(gitmodules_update_clone_config, the_repository, max_jobs); + config_from_gitmodules(gitmodules_update_clone_config, repo, max_jobs); } diff --git a/submodule-config.h b/submodule-config.h index f55d4e3b61..755570d5d1 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -57,7 +57,8 @@ int option_fetch_parse_recurse_submodules(const struct option *opt, int parse_update_recurse_submodules_arg(const char *opt, const char *arg); int parse_push_recurse_submodules_arg(const char *opt, const char *arg); void repo_read_gitmodules(struct repository *repo, int skip_if_read); -void gitmodules_config_oid(const struct object_id *commit_oid); +void gitmodules_config_oid(struct repository *repo, + const struct object_id *commit_oid); /** * Same as submodule_from_path but lookup by name. @@ -80,7 +81,8 @@ const struct submodule *submodule_from_path(struct repository *r, void submodule_free(struct repository *r); int print_config_from_gitmodules(struct repository *repo, const char *key); -int config_set_in_gitmodules_file_gently(const char *key, const char *value); +int config_set_in_gitmodules_file_gently(struct repository *repo, + const char *key, const char *value); /* * Returns 0 if the name is syntactically acceptable as a submodule "name" @@ -100,8 +102,10 @@ int check_submodule_url(const char *url); * New helpers to retrieve arbitrary configuration from the '.gitmodules' file * should NOT be added. */ -void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules); -void update_clone_config_from_gitmodules(int *max_jobs); +void fetch_config_from_gitmodules(struct repository *repo, + int *max_children, + int *recurse_submodules); +void update_clone_config_from_gitmodules(struct repository *repo, int *max_jobs); /* * Submodule entry that contains relevant information about a diff --git a/submodule.c b/submodule.c index 5c92575888..6fcb606f7e 100644 --- a/submodule.c +++ b/submodule.c @@ -133,7 +133,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) strbuf_addstr(&entry, "submodule."); strbuf_addstr(&entry, submodule->name); strbuf_addstr(&entry, ".path"); - ret = config_set_in_gitmodules_file_gently(entry.buf, newpath); + ret = config_set_in_gitmodules_file_gently(the_repository, entry.buf, newpath); strbuf_release(&entry); return ret; } diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c index 3c5c4c4a09..ea9bef0904 100644 --- a/t/helper/test-submodule.c +++ b/t/helper/test-submodule.c @@ -168,7 +168,7 @@ static int cmd__submodule_config_set(int argc, const char **argv) if (!is_writing_gitmodules_ok()) die("please make sure that the .gitmodules file is in the working tree"); - return config_set_in_gitmodules_file_gently(argv[1], argv[2]); + return config_set_in_gitmodules_file_gently(the_repository, argv[1], argv[2]); } usage_with_options(usage, options); } @@ -188,7 +188,7 @@ static int cmd__submodule_config_unset(int argc, const char **argv) if (argc == 2) { if (!is_writing_gitmodules_ok()) die("please make sure that the .gitmodules file is in the working tree"); - return config_set_in_gitmodules_file_gently(argv[1], NULL); + return config_set_in_gitmodules_file_gently(the_repository, argv[1], NULL); } usage_with_options(usage, options); } From e4ad79fd9d8984827b2dc304f1ed99fa69e1ad11 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:47 +0200 Subject: [PATCH 04/13] submodule-config: stop using `the_hash_algo` We have two uses of `the_hash_algo` in "submodule-config.c": - One trivial use in `gitmodules_cb`, which we can convert to use the hash algorithm of the repository that's already available in the caller's context. - One use where we compute the hashmap key of an object ID. We should only ever get valid, populated object IDs here, and consequently we can easily adapt that function to use the hash algorithm of the passed-in object ID. Adapt both sites accordingly. Safeguard us against the case where the passed-in object ID is _not_ properly initialized. While this case shouldn't ever happen, it doesn't hurt to be defensive. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- submodule-config.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/submodule-config.c b/submodule-config.c index f8c2cf7a93..7c73fa108b 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -133,7 +133,9 @@ void submodule_cache_free(struct submodule_cache *cache) static unsigned int hash_oid_string(const struct object_id *oid, const char *string) { - return memhash(oid->hash, the_hash_algo->rawsz) + strhash(string); + if (oid->algo == GIT_HASH_UNKNOWN) + BUG("hashing an object ID with unknown algorithm"); + return memhash(oid->hash, hash_algos[oid->algo].rawsz) + strhash(string); } static void cache_put_path(struct submodule_cache *cache, @@ -824,7 +826,7 @@ static int gitmodules_cb(const char *var, const char *value, parameter.cache = repo->submodule_cache; parameter.treeish_name = NULL; - parameter.gitmodules_oid = null_oid(the_hash_algo); + parameter.gitmodules_oid = null_oid(repo->hash_algo); parameter.overwrite = 1; return parse_config(var, value, ctx, ¶meter); From a532feba892a3034d04b379e61c3cb18756007d1 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:48 +0200 Subject: [PATCH 05/13] submodule-config: stop registering submodule sources When reading the ".gitmodules" file from a blob in a repository other than `the_repository`, we register that repository's object database as an in-memory source of `the_repository`'s object database. This call has its origins in d9b8b8f896 (submodule-config.c: use repo_get_oid for reading .gitmodules, 2019-04-16): back then, `config_with_options()` was not able to read a blob from an arbitrary repository, but would always read it via `the_repository`. So even though the blob could be resolved in the submodule repository via `repo_get_oid()`, the submodule's object database had to be registered as an in-memory source of `the_repository` so that the subsequent object read was able to find the blob at all. That need went away with e3e8bf046e (submodule-config: pass repo upon blob config read, 2021-08-16), which taught the config machinery to read the blob from the repository we pass to it. The same series converted the eager submodule source registration into a lazy mechanism that only registers submodule sources with the object database when an object lookup failed. The intent though was that we don't ever have to fall back to this mechanism in the first place, and to verify that this is the case we introduced GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB. If set, then any such lazy registration would cause us to BUG. At the beginning of this series, we still triggered this bug in t1092. But now that we have converted the "cache-tree" subsystem to not depend on `the_repository` anymore it also knows to properly access objects via the submodule. With that change, GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB does not cause any failures anymore. Remove the call to `odb_add_submodule_source_by_path()`. This removes the last user of `the_repository`, so at the same time we can also get rid of `USE_THE_REPOSITORY_VARIABLE`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- submodule-config.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/submodule-config.c b/submodule-config.c index 7c73fa108b..37c3be377b 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -803,9 +802,6 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void } else if (repo_get_oid(repo, GITMODULES_INDEX, &oid) >= 0 || repo_get_oid(repo, GITMODULES_HEAD, &oid) >= 0) { config_source.blob = oidstr = xstrdup(oid_to_hex(&oid)); - if (repo != the_repository) - odb_add_submodule_source_by_path(the_repository->objects, - repo->objects->sources->path); } else { goto out; } From 93cd344e34f6f0baab4525fbd5074d53fee4d3b9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:49 +0200 Subject: [PATCH 06/13] builtin/grep: stop registering submodule ODB as source Same as with the preceding commit, git-grep(1) registers each submodule's object database as an in-memory source of the main object database before grepping it. This was introduced as an eager alternate registration and converted into the lazy mechanism via 8d33c3af0b (grep: use submodule-ODB-as-alternate lazy-addition, 2021-08-16). Starting with 0693806bf8 (grep: add repository to OID grep sources, 2021-08-16), the command instead knows to pass submodule repositories to our workers, which means that those now use that repository to look up objects, too. As a consequence, registering submodule sources as alternates is not required anymore. Remove the logic to register submodule sources. Unfortunately, this does not allow us to get rid of the object read lock as initializing the subrepository is still racy. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/grep.c | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/builtin/grep.c b/builtin/grep.c index 073dfaaf45..b045f8a488 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -463,16 +463,6 @@ static int grep_submodule(struct grep_opt *opt, ALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc); repos_to_free[repos_to_free_nr++] = subrepo; - /* - * NEEDSWORK: repo_read_gitmodules() might call - * odb_add_to_alternates_memory() via config_from_gitmodules(). This - * operation causes a race condition with concurrent object readings - * performed by the worker threads. That's why we need obj_read_lock() - * here. It should be removed once it's no longer necessary to add the - * subrepo's odbs to the in-memory alternates list. - */ - obj_read_lock(); - /* * NEEDSWORK: when reading a submodule, the sparsity settings in the * superproject are incorrectly forgotten or misused. For example: @@ -498,18 +488,14 @@ static int grep_submodule(struct grep_opt *opt, * ditto. * * Note that this list is not exhaustive. + * + * NEEDSWORK: initializing the subrepository is not thread-safe, + * either, as it may cause us to race around `get_main_ref_store()`. We + * thus need to hold the object-read lock to serialize all readers with + * one another. */ + obj_read_lock(); repo_read_gitmodules(subrepo, 0); - - /* - * All code paths tested by test code no longer need submodule ODBs to - * be added as alternates, but add it to the list just in case. - * Submodule ODBs added through add_submodule_odb_by_path() will be - * lazily registered as alternates when needed (and except in an - * unexpected code interaction, it won't be needed). - */ - odb_add_submodule_source_by_path(the_repository->objects, - subrepo->objects->sources->path); obj_read_unlock(); memcpy(&subopt, opt, sizeof(subopt)); From 5c5dfbeef6ab58debad8daaa43dfdd0370c48301 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:50 +0200 Subject: [PATCH 07/13] odb: remove infrastructure to register submodule sources The preceding commits have removed the last two users of `odb_add_submodule_source_by_path()`. The mechanism was only ever meant as a transitional crutch while migrating submodule object access away from "add the submodule ODB as an alternate of the_repository" towards explicitly passing the submodule repository, see a35e03dee0 (submodule: lazily add submodule ODBs as alternates, 2021-08-16). Remove it. As GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB is now a no-op, remove its documentation and the exports from the test suite, as well. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 36 -------------------------- odb.h | 14 ---------- t/README | 7 ----- t/t5526-fetch-submodules.sh | 3 --- t/t5531-deep-submodule-push.sh | 3 --- t/t5545-push-options.sh | 3 --- t/t5572-pull-submodule.sh | 3 --- t/t6437-submodule-merge.sh | 3 --- t/t7418-submodule-sparse-gitmodules.sh | 3 --- t/t7814-grep-recurse-submodules.sh | 3 --- 10 files changed, 78 deletions(-) diff --git a/odb.c b/odb.c index 6d5943e5ea..2f8a70a90c 100644 --- a/odb.c +++ b/odb.c @@ -388,12 +388,6 @@ struct odb_source *odb_find_source_or_die(struct object_database *odb, const cha return source; } -void odb_add_submodule_source_by_path(struct object_database *odb, - const char *path) -{ - string_list_insert(&odb->submodule_source_paths, path); -} - static void fill_alternate_refs_command(struct repository *repo, struct child_process *cmd, const char *repo_path) @@ -549,23 +543,6 @@ void disable_obj_read_lock(void) pthread_mutex_destroy(&obj_read_mutex); } -static int register_all_submodule_sources(struct object_database *odb) -{ - int ret = odb->submodule_source_paths.nr; - - for (size_t i = 0; i < odb->submodule_source_paths.nr; i++) - odb_add_to_alternates_memory(odb, - odb->submodule_source_paths.items[i].string); - if (ret) { - string_list_clear(&odb->submodule_source_paths, 0); - trace2_data_intmax("submodule", odb->repo, - "register_all_submodule_sources/registered", ret); - if (git_env_bool("GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB", 0)) - BUG("register_all_submodule_sources() called"); - } - return ret; -} - static enum odb_read_status do_oid_object_info_extended(struct object_database *odb, const struct object_id *oid, struct object_info *oi, unsigned flags) @@ -614,16 +591,6 @@ static enum odb_read_status do_oid_object_info_extended(struct object_database * } } - /* - * This might be an attempt at accessing a submodule object as - * if it were in main object store (having called - * `odb_add_submodule_source_by_path()` on that submodule's - * ODB). If any such ODBs exist, register them and try again. - */ - if (register_all_submodule_sources(odb)) - /* We added some alternates; retry */ - continue; - /* Check if it is a missing object */ if (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) && !already_retried && @@ -1109,7 +1076,6 @@ struct object_database *odb_new(struct repository *repo, CALLOC_ARRAY(o, 1); o->repo = repo; pthread_mutex_init(&o->replace_mutex, NULL); - string_list_init_dup(&o->submodule_source_paths); hashmap_init(&o->source_by_path, odb_source_by_path_cmp, o, 0); o->source_paths_icase = -1; @@ -1166,8 +1132,6 @@ void odb_free(struct object_database *o) odb_close(o); odb_free_sources(o); - string_list_clear(&o->submodule_source_paths, 0); - free(o); } diff --git a/odb.h b/odb.h index 248ee9cdfa..54548efc55 100644 --- a/odb.h +++ b/odb.h @@ -89,12 +89,6 @@ struct object_database { unsigned long object_count; unsigned object_count_flags; unsigned object_count_valid : 1; - - /* - * Submodule source paths that will be added as additional sources to - * allow lookup of submodule objects via the main object database. - */ - struct string_list submodule_source_paths; }; enum odb_new_flags { @@ -224,14 +218,6 @@ void odb_restore_primary_source(struct object_database *odb, struct odb_source *restore_source, const char *old_path); -/* - * Call odb_add_submodule_source_by_path() to add the submodule at the given - * path to a list. The object stores of all submodules in that list will be - * added as additional sources in the object store when looking up objects. - */ -void odb_add_submodule_source_by_path(struct object_database *odb, - const char *path); - /* * Iterate through all alternates of the database and execute the provided * callback function for each of them. Stop iterating once the callback diff --git a/t/README b/t/README index 9a9daaf2af..f831c5355b 100644 --- a/t/README +++ b/t/README @@ -462,13 +462,6 @@ GIT_TEST_CHECKOUT_WORKERS= overrides the 'checkout.workers' setting to and 'checkout.thresholdForParallelism' to 0, forcing the execution of the parallel-checkout code. -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=, when true, makes -registering submodule ODBs as alternates a fatal action. Support for -this environment variable can be removed once the migration to -explicitly providing repositories when accessing submodule objects is -complete or needs to be abandoned for whatever reason (in which case the -migrated codepaths still retain their performance benefits). - GIT_TEST_REQUIRE_PREREQ= allows specifying a space separated list of prereqs that are required to succeed. If a prereq in this list is triggered by a test and then fails then the whole test run will abort. This can help to make diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 7b3b7359da..37d7373b36 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -3,9 +3,6 @@ test_description='Recursive "git fetch" for submodules' -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - . ./test-lib.sh pwd=$(pwd) diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 7d239dd31f..73429ec6e3 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -5,9 +5,6 @@ test_description='test push with submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - . ./test-lib.sh test_expect_success setup ' diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh index fb13549da7..239edd7d62 100755 --- a/t/t5545-push-options.sh +++ b/t/t5545-push-options.sh @@ -5,9 +5,6 @@ test_description='pushing to a repository using push options' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - . ./test-lib.sh mk_repo_pair () { diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index 42d14328b6..9969a3294e 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -2,9 +2,6 @@ test_description='pull can handle submodules' -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 107e13afbc..1546d5f773 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -5,9 +5,6 @@ test_description='merging with submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - . ./test-lib.sh # diff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh index dde11ecce8..cf94e30e78 100755 --- a/t/t7418-submodule-sparse-gitmodules.sh +++ b/t/t7418-submodule-sparse-gitmodules.sh @@ -12,9 +12,6 @@ The test setup uses a sparse checkout, however the same scenario can be set up also by committing .gitmodules and then just removing it from the filesystem. ' -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index e1cf53dc9e..3d149d34c1 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -9,9 +9,6 @@ submodules. TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh -GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 -export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB - test_expect_success 'setup directory structure and submodule' ' echo "(1|2)d(3|4)" >a && mkdir b && From 60942e28b3c49f462c820ec95f30624464a11764 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:51 +0200 Subject: [PATCH 08/13] tmp-objdir: drop unused function to register alternate The last caller of `tmp_objdir_add_as_alternate()` went away in bdee7b3013 (builtin/receive-pack: stage incoming objects via ODB transactions, 2026-07-10) and is unused now. Remove the function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- tmp-objdir.c | 5 ----- tmp-objdir.h | 6 ------ 2 files changed, 11 deletions(-) diff --git a/tmp-objdir.c b/tmp-objdir.c index 0eaa79ffd7..deaaf6ba2e 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c @@ -321,11 +321,6 @@ const char **tmp_objdir_env(const struct tmp_objdir *t) return t->env.v; } -void tmp_objdir_add_as_alternate(const struct tmp_objdir *t) -{ - odb_add_to_alternates_memory(t->repo->objects, t->path.buf); -} - struct odb_source *tmp_objdir_replace_primary_odb(struct tmp_objdir *t, int will_destroy) { diff --git a/tmp-objdir.h b/tmp-objdir.h index 81eb927413..05f0d08d10 100644 --- a/tmp-objdir.h +++ b/tmp-objdir.h @@ -55,12 +55,6 @@ int tmp_objdir_destroy(struct tmp_objdir *); */ void tmp_objdir_discard_objects(struct tmp_objdir *); -/* - * Add the temporary object directory as an alternate object store in the - * current process. - */ -void tmp_objdir_add_as_alternate(const struct tmp_objdir *); - /* * Replaces the writable object store in the current process with the temporary * object directory and makes the former main object store an alternate. From a5a7e86df19a720a611d4a149fd77435414ac62f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:52 +0200 Subject: [PATCH 09/13] odb/packed: fix memory leaks when freeing source When freeing a "packed" source we don't close either its packs nor its multi-pack indices. This can cause memory leaks in case we create an ad-hoc packed source. As we used to always link packed sources to the main object database we never noticed this issue until now, but it's going to surface in subsequent commits where we stop linking them. Plug the memory leaks by closing the source first. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb/source-packed.c | 1 + 1 file changed, 1 insertion(+) diff --git a/odb/source-packed.c b/odb/source-packed.c index 1d90e714e6..166e76e2d6 100644 --- a/odb/source-packed.c +++ b/odb/source-packed.c @@ -844,6 +844,7 @@ static void odb_source_packed_free(struct odb_source *source) chdir_notify_unregister(odb_source_packed_reparent, packed); + odb_source_close(source); for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) free(e->pack); packfile_list_clear(&packed->packs); From 314b468c68de5a61f5d3b35ada15ee9cd6ba875d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:53 +0200 Subject: [PATCH 10/13] builtin/multi-pack-index: refuse unknown sources with "--object-dir=" Users can tell git-multi-pack-index(1) to access multi-pack indices that are stored in a different object directory via the "--object-dir=" option. This allows them to for example write or verify a multi-pack index other than the one located in the main object directory in case a repository has alternates with multiple multi-pack indices. But while the documentation explicitly points out that the specified object directory must be an alternate of the current repository, we never verify that property. Instead, starting with 017db7bb14 (midx: load multi-pack indices via their source, 2025-08-11), we now construct an ad-hoc source and link it to the main object directory. Besides contradicting the documentation, it's dubious that this really ought to work in the first place: creating a multi-pack index (and potentially a bitmap) for a completely foreign object directory is of questionable value, as bitmap commit selection operates on the invoking repository's refs. Furthermore, this is the only remaining caller outside of our test helpers that constructs an ad-hoc source and links it to the database, and we want to get rid of this mechanism as part of this series. Stop constructing the ad-hoc source and instead refuse the operation. While this results in a change in behaviour, this restriction has been documented as such ever since f57a739691 (midx: avoid opening multiple MIDXs when writing, 2021-09-01). Note that this change requires us to adapt one test chain in t5319, as it creates an object directory that is not connected to any repository and then uses it via "--object-dir=". The setup itself already documents this and does the necessary gymnastics to link the object directory to a temporary repository, but subsequent tests don't. Adapt those tests to retain and reuse the temporary repository. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/multi-pack-index.c | 3 ++- t/t5319-multi-pack-index.sh | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 6e73c85cde..753bd53a70 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -90,7 +90,8 @@ static struct odb_source_files *handle_object_dir_option(struct repository *repo { struct odb_source *source = odb_find_source(repo->objects, opts.object_dir); if (!source) - source = odb_add_to_alternates_memory(repo->objects, opts.object_dir); + die(_("object directory is not an alternate of the current repository: '%s'"), + opts.object_dir); return odb_source_files_downcast(source); } diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 68143cb5b7..00e90f163f 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -698,10 +698,9 @@ test_expect_success 'force some 64-bit offsets with pack-objects' ' corrupt_data $idx64 $(test_oid idxoff) "\02" && # objects64 is not a real repository, but can serve as an alternate # anyway so we can write a MIDX into it - git init repo && - test_when_finished "rm -fr repo" && + git init repo64 && ( - cd repo && + cd repo64 && ( cd ../objects64 && pwd ) >.git/objects/info/alternates && midx64=$(git multi-pack-index --object-dir=../objects64 write) ) && @@ -709,7 +708,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' ' ' test_expect_success 'verify multi-pack-index with 64-bit offsets' ' - git multi-pack-index verify --object-dir=objects64 + git -C repo64 multi-pack-index verify --object-dir=../objects64 ' NUM_OBJECTS=63 @@ -721,7 +720,7 @@ MIDX_BYTE_LARGE_OFFSET=$(($MIDX_OFFSET_LARGE_OFFSETS + 3)) test_expect_success 'verify incorrect 64-bit offset' ' corrupt_midx_and_verify $MIDX_BYTE_LARGE_OFFSET "\07" objects64 \ - "incorrect object offset" + "incorrect object offset" "git -C repo64 multi-pack-index verify --object-dir=../objects64" ' test_expect_success 'setup expire tests' ' From f1d88b9698c4d9965fa72f1ce5739836ab97940f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:54 +0200 Subject: [PATCH 11/13] t/helper: adapt read-midx to not link ad-hoc source anymore Same as in the preceding commit, refactor the setup of ad-hoc object database sources when accessing a multi-pack index in an arbitrary location to not link the newly created source into the main object database anymore. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/helper/test-read-midx.c | 47 +++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index 27a05da957..1f7a1927e4 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -5,34 +5,42 @@ #include "midx.h" #include "repository.h" #include "odb.h" +#include "odb/source-packed.h" #include "pack-bitmap.h" #include "packfile.h" #include "setup.h" #include "gettext.h" #include "pack-revindex.h" -static struct multi_pack_index *setup_midx(const char *object_dir) +static struct multi_pack_index *setup_midx(const char *object_dir, + struct odb_source_packed **out) { - struct odb_source_files *files; + struct odb_source_packed *packed; struct odb_source *source; - setup_git_directory(the_repository); - source = odb_find_source(the_repository->objects, object_dir); - if (!source) - source = odb_add_to_alternates_memory(the_repository->objects, - object_dir); - files = odb_source_files_downcast(source); - return load_multi_pack_index(files->packed); + setup_git_directory(the_repository); + + source = odb_find_source(the_repository->objects, object_dir); + if (source) { + packed = odb_source_files_downcast(source)->packed; + } else { + packed = odb_source_packed_new(the_repository->objects, + object_dir, false); + *out = packed; + } + + return load_multi_pack_index(packed); } static int read_midx_file(const char *object_dir, const char *checksum, int show_objects) { + struct odb_source_packed *packed = NULL; uint32_t i; struct multi_pack_index *m, *tip; int ret = 0; - m = tip = setup_midx(object_dir); + m = tip = setup_midx(object_dir, &packed); if (!m) return 1; @@ -91,29 +99,35 @@ static int read_midx_file(const char *object_dir, const char *checksum, out: close_midx(tip); + if (packed) + odb_source_free(&packed->base); return ret; } static int read_midx_checksum(const char *object_dir) { + struct odb_source_packed *packed = NULL; struct multi_pack_index *m; - m = setup_midx(object_dir); + m = setup_midx(object_dir, &packed); if (!m) return 1; printf("%s\n", midx_get_checksum_hex(m)); close_midx(m); + if (packed) + odb_source_free(&packed->base); return 0; } static int read_midx_preferred_pack(const char *object_dir) { + struct odb_source_packed *packed = NULL; struct multi_pack_index *midx = NULL; uint32_t preferred_pack; - midx = setup_midx(object_dir); + midx = setup_midx(object_dir, &packed); if (!midx) return 1; @@ -124,17 +138,21 @@ static int read_midx_preferred_pack(const char *object_dir) } printf("%s\n", midx->pack_names[preferred_pack]); + close_midx(midx); + if (packed) + odb_source_free(&packed->base); return 0; } static int read_midx_bitmapped_packs(const char *object_dir) { + struct odb_source_packed *packed = NULL; struct multi_pack_index *midx = NULL; struct bitmapped_pack pack; uint32_t i; - midx = setup_midx(object_dir); + midx = setup_midx(object_dir, &packed); if (!midx) return 1; @@ -150,7 +168,8 @@ static int read_midx_bitmapped_packs(const char *object_dir) } close_midx(midx); - + if (packed) + odb_source_free(&packed->base); return 0; } From 46bbc8651579a572040c41bf7ff5d698e62758f7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:55 +0200 Subject: [PATCH 12/13] t/helper: stop registering alternates in "ref-store" command When using the "ref-store" command we support access to multiple different reference stores. As part of that we allow the caller to explicitly exercise stores of a submodule. This allows us to verify low-level behaviour of submodule stores, which is exercised in t1406. When doing so we also link the submodule's object database into the main object database. The intent of this is that it allows us to access objects of the submodule, too. But that functionality is not even needed anymore: when creating a submodule reference store, we will first initialize the submodule repository and then initialize the store with that repository. And as the reference subsystem doesn't depend on `the_repository` anymore all subsequent object lookups performed by the reference store will be routed to the submodule repository. It is thus not needed anymore to register the submodule object store with the main object database. Remove the call. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/helper/test-ref-store.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 5a9a3053d9..db58f00589 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -74,14 +74,6 @@ static const char **get_store(const char **argv, struct ref_store **refs) } else if (!strcmp(argv[0], "main")) { *refs = get_main_ref_store(the_repository); } else if (skip_prefix(argv[0], "submodule:", &gitdir)) { - struct strbuf sb = STRBUF_INIT; - - if (!repo_submodule_path_append(the_repository, - &sb, gitdir, "objects/")) - die("computing submodule path failed"); - odb_add_to_alternates_memory(the_repository->objects, sb.buf); - strbuf_release(&sb); - *refs = repo_get_submodule_ref_store(the_repository, gitdir); } else if (skip_prefix(argv[0], "worktree:", &gitdir)) { struct worktree **p, **worktrees = get_worktrees(the_repository); From f0eb23a8f57d06cd461e72b35caf500145980ce8 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Sep 2026 07:51:56 +0200 Subject: [PATCH 13/13] odb: remove the ability to link sources ad-hoc Over the course of this patch series we have adapted all callers of `odb_add_to_alternates_memory()` to not do so anymore. Remove the function. This series of refactorings doesn't only simplify our code base. More importantly, with those changes in place we can now unconditionally assume that the list of sources linked to the object database only consists of the primary source and its alternates. This serves as the foundation to eventually move handling of alternates into the "files" backend itself. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 6 ------ odb.h | 8 -------- 2 files changed, 14 deletions(-) diff --git a/odb.c b/odb.c index 2f8a70a90c..5fe081496f 100644 --- a/odb.c +++ b/odb.c @@ -247,12 +247,6 @@ void odb_add_to_alternates_file(struct object_database *odb, odb_add_alternate_recursively(odb, dir, 0); } -struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, - const char *dir) -{ - return odb_add_alternate_recursively(odb, dir, 0); -} - struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, const char *dir, int will_destroy, struct odb_source **prev_source) diff --git a/odb.h b/odb.h index 54548efc55..9025239df5 100644 --- a/odb.h +++ b/odb.h @@ -258,14 +258,6 @@ int odb_has_alternates(struct object_database *odb); void odb_add_to_alternates_file(struct object_database *odb, const char *dir); -/* - * Add the directory to the in-memory list of alternate sources (along with any - * recursive alternates it points to), but do not modify the on-disk alternates - * file. - */ -struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, - const char *dir); - /* * Read an object from the database. Returns the object data and assigns object * type and size to the `type` and `size` pointers, if these pointers are