From 1c46ce6dda5c58301af6a8b7a27e68fd7fb86993 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 17 Aug 2026 13:09:21 +0200 Subject: [PATCH 1/5] setup: create ref and object databases after config is written When creating a new repository we create both the reference and object databases after we have finalized the repository. This ensures that those subsystems find a fully-configured repository at the time where they are asked to create their own on-disk data structures. There is one exception though: while we have already fully configured the repository at this point, we haven't yet written both "core.sharedRepository" and "receive.denyNonFastforwards". The latter configuration doesn't really matter to us, but the first one does as the "files" object database source reads it. This doesn't cause any problems right now, but it will in a subsequent patch where we will start to read "core.ignoreCase" when creating the object database. Move the initialization of both of these data structures towards the end of `init_db()`. The only thing that now comes after is status reporting, but that's it. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- setup.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.c b/setup.c index 20d29f31f4..d90654f584 100644 --- a/setup.c +++ b/setup.c @@ -2880,12 +2880,6 @@ int init_db(struct repository *repo, reinit = create_default_files(repo, template_dir, original_git_dir, &repo_fmt, init_shared_repository); - if (!(flags & INIT_DB_SKIP_REFDB)) - create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); - create_object_database(repo); - - startup_info->have_repository = 1; - if (repo_settings_get_shared_repository(repo)) { char buf[10]; /* We do not spell "group" and such, so that @@ -2907,6 +2901,12 @@ int init_db(struct repository *repo, repo_config_set(repo, "receive.denyNonFastforwards", "true"); } + if (!(flags & INIT_DB_SKIP_REFDB)) + create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); + create_object_database(repo); + + startup_info->have_repository = 1; + if (!(flags & INIT_DB_QUIET)) { int len = strlen(git_dir); From 987927709135a2267abfa929a6f20ddd4302c8b7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 17 Aug 2026 13:09:22 +0200 Subject: [PATCH 2/5] odb: decouple source path comparisons from `the_repository` When registering alternates we deduplicate object database sources by their path so that the same source won't be added twice. Ever since cf2dc1c238 (speed up alt_odb_usable() with many alternates, 2021-07-07) this duplicate check is backed by a map keyed by the source's path, using `fspathhash()` and `fspatheq()` as hash and equality functions, respectively. These functions are problematic in this context for two reasons: - They implicitly depend on `the_repository` instead of the repository that owns the object database. - They derive case-sensitivity from `repo_ignore_case()`, which returns a default value in case the repository's configuration has not been parsed yet. Object database sources may be registered before that is the case, so the answer may flip depending on when a source gets registered. Fix this by making the comparison self-contained in the object database. Instead of using `fspathhash()` and `fspatheq()` we resolve "core.ignoreCase" manually and then use the correct comparison function based on the result. This requires us to migrate to a `struct hashmap`, as the khash interface does not give us the ability to pass an arbitrary payload to these functions, and hence we'd have to use global state to decide which of those to use. Note that we can unconditionally use `strihash()` to compute entry hashes regardless of case sensitivity: a hash function only needs to guarantee that equal keys have equal hashes, and a case-insensitive hash satisfies this requirement for both case-sensitive and case-insensitive equality. Overall it's quite debatable whether all of this complexity really is worth it, out of two reasons: - We could linearly search through all sources to find duplicates. But the mentioned commit cares about cases with thousands of alternates, and a linear search would of course regress performance quite a bit. This doesn't really feel like a reasonable case to care about, but I don't feel comfortable regressing it anyway. - It's dubious whether we should handle "core.ignoreCase" in the first place. The downside would be that we might add the same alternate multiple times with different casing. But this is an edge case, and it's not even fully fixed because we don't resolve symlinks or mountpoints, either. So for now, keep this infrastructure in-place while removing the global dependency on `the_repository`. We may want to revisit this in the future though. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 78 ++++++++++++++++++++++++++++++++++++++-------------- odb.h | 15 +++++++++- odb/source.h | 7 +++++ 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/odb.c b/odb.c index bd02d8ad54..22f1425ba5 100644 --- a/odb.c +++ b/odb.c @@ -2,11 +2,10 @@ #include "abspath.h" #include "commit-graph.h" #include "config.h" -#include "dir.h" #include "environment.h" #include "gettext.h" +#include "hashmap.h" #include "hex.h" -#include "khash.h" #include "lockfile.h" #include "loose.h" #include "midx.h" @@ -29,8 +28,47 @@ #include "trace2.h" #include "write-or-die.h" -KHASH_INIT(odb_path_map, const char * /* key: odb_path */, - struct odb_source *, 1, fspathhash, fspatheq) +/* + * NEEDSWORK: we're using "core.ignoreCase" to deduplicate alternates that + * _may_ be the same. This requires quite a bit of boilerplate for dubious + * benefit: + * + * - Duplicating alternates should really only lead to regressed performance. + * + * - We don't properly resolve symlinks or mointpoints, so we may still end + * up duplicating alternates. + * + * - The value may be lying, in which case we might deduplicate alternates + * that are in fact not mapping to the same directory. + * + * We should investigate whether we can remove this whole mechanism outright. + */ +static int odb_source_paths_cmp(struct object_database *o, + const char *a, const char *b) +{ + if (o->source_paths_icase < 0) { + int icase = 0; + repo_config_get_bool(o->repo, "core.ignorecase", &icase); + o->source_paths_icase = icase; + } + + return o->source_paths_icase ? strcasecmp(a, b) : strcmp(a, b); +} + +static int odb_source_by_path_cmp(const void *cb_data, + const struct hashmap_entry *entry, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + struct object_database *o = (struct object_database *)cb_data; + const struct odb_source *source = container_of(entry, const struct odb_source, by_path_entry); + const char *path = keydata; + + if (!path) + path = container_of(entry_or_key, const struct odb_source, by_path_entry)->path; + + return odb_source_paths_cmp(o, source->path, path); +} int odb_mkstemp(struct object_database *odb, struct strbuf *temp_filename, const char *pattern) @@ -58,8 +96,8 @@ int odb_mkstemp(struct object_database *odb, */ static bool odb_is_source_usable(struct object_database *o, const char *path) { - int r; struct strbuf normalized_objdir = STRBUF_INIT; + struct hashmap_entry key; bool usable = false; strbuf_realpath(&normalized_objdir, o->sources->path, 1); @@ -76,20 +114,18 @@ static bool odb_is_source_usable(struct object_database *o, const char *path) * Prevent the common mistake of listing the same * thing twice, or object directory itself. */ - if (!o->source_by_path) { - khiter_t p; - - o->source_by_path = kh_init_odb_path_map(); + if (!hashmap_get_size(&o->source_by_path)) { assert(!o->sources->next); - p = kh_put_odb_path_map(o->source_by_path, o->sources->path, &r); - assert(r == 1); /* never used */ - kh_value(o->source_by_path, p) = o->sources; + hashmap_entry_init(&o->sources->by_path_entry, + strihash(o->sources->path)); + hashmap_add(&o->source_by_path, &o->sources->by_path_entry); } - if (fspatheq(path, normalized_objdir.buf)) + if (!odb_source_paths_cmp(o, path, normalized_objdir.buf)) goto out; - if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path)) + hashmap_entry_init(&key, strihash(path)); + if (hashmap_get(&o->source_by_path, &key, path)) goto out; usable = true; @@ -172,8 +208,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * { struct odb_source *alternate = NULL; struct strvec sources = STRVEC_INIT; - khiter_t pos; - int ret; if (!odb_is_source_usable(odb, source)) goto error; @@ -184,10 +218,11 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * *odb->sources_tail = alternate; odb->sources_tail = &(alternate->next); - pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret); - if (!ret) + hashmap_entry_init(&alternate->by_path_entry, strihash(alternate->path)); + if (hashmap_get(&odb->source_by_path, &alternate->by_path_entry, + alternate->path)) BUG("source must not yet exist"); - kh_value(odb->source_by_path, pos) = alternate; + hashmap_add(&odb->source_by_path, &alternate->by_path_entry); /* recursively add alternates */ odb_source_read_alternates(alternate, &sources); @@ -1056,6 +1091,8 @@ struct object_database *odb_new(struct repository *repo, 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; if (flags & ODB_NEW_HONOR_ENV) { primary_source = xstrdup_or_null(getenv(DB_ENVIRONMENT)); @@ -1094,8 +1131,7 @@ static void odb_free_sources(struct object_database *o) odb_source_free(o->inmemory_objects); o->inmemory_objects = NULL; - kh_destroy_odb_path_map(o->source_by_path); - o->source_by_path = NULL; + hashmap_clear(&o->source_by_path); } void odb_free(struct object_database *o) diff --git a/odb.h b/odb.h index 8eb4e85d64..71af7450a9 100644 --- a/odb.h +++ b/odb.h @@ -1,6 +1,7 @@ #ifndef ODB_H #define ODB_H +#include "hashmap.h" #include "object.h" #include "oidset.h" #include "oidmap.h" @@ -54,7 +55,19 @@ struct object_database { */ struct odb_source *sources; struct odb_source **sources_tail; - struct kh_odb_path_map *source_by_path; + + /* + * Map of object database sources, keyed by their respective paths. + * This map is used to detect the case where the same source is + * registered multiple times. + */ + struct hashmap source_by_path; + + /* + * Whether source paths shall be compared case-insensitively, as + * determined by "core.ignoreCase". + */ + int source_paths_icase; int loaded_alternates; diff --git a/odb/source.h b/odb/source.h index 4bc037b8d6..82cda8ad75 100644 --- a/odb/source.h +++ b/odb/source.h @@ -1,6 +1,7 @@ #ifndef ODB_SOURCE_H #define ODB_SOURCE_H +#include "hashmap.h" #include "object.h" #include "odb.h" #include "odb/transaction.h" @@ -50,6 +51,12 @@ struct strvec; struct odb_source { struct odb_source *next; + /* + * Entry in the object database's map of sources, keyed by this + * source's path. + */ + struct hashmap_entry by_path_entry; + /* Object database that owns this object source. */ struct object_database *odb; From f978f560dd7f0c92ed1834198747da621400350a Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 17 Aug 2026 13:09:23 +0200 Subject: [PATCH 3/5] odb: eagerly initialize alternates When creating the object database we initialize the main object database source, but we don't yet initialize its alternates. Instead, we have many calls to `odb_prepare_alternates()` cluttered around the code base whenever we are about to iterate through the sources. This lazy loading doesn't really add much value: the moment where we read any object we _have_ to load the alternates anyway. So given that most of our commands would access the object database this optimization is not really buying us much in the first place. Quite on the contrary, it makes the code harder to understand and is a potential source of bugs in case any callsite forgot to prepare alternates before we iterate through the sources. Historically though there was a reason why we deferred lazy-loading: it may happen that the repository has "core.ignoreCase" configured, and we use that to deduplicate the list of alternates in case we had the same alternate configured multiple times, but with different casing. We used to initialize the object database before we had fully configured the owning repository though, and consequently we couldn't access that configuration yet. This has changed in the preceding commit though where we started to parse "core.ignoreCase" manually. Eagerly prepare alternates both when creating the object database and when flushing its caches. Drop the now-unneeded calls to prepare the alternates that are scattered across the code base. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/fsck.c | 3 --- builtin/pack-objects.c | 3 --- commit-graph.c | 4 ---- loose.c | 1 - object-name.c | 1 - odb.c | 26 ++++---------------------- odb.h | 6 ------ odb/streaming.c | 1 - pack-bitmap.c | 2 -- packfile.c | 1 - packfile.h | 2 -- 11 files changed, 4 insertions(+), 46 deletions(-) diff --git a/builtin/fsck.c b/builtin/fsck.c index a6c054e45b..892c5661d9 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -1069,7 +1069,6 @@ int cmd_fsck(int argc, odb_for_each_object(repo->objects, NULL, mark_object_for_connectivity, repo, 0); } else { - odb_prepare_alternates(repo->objects); for (source = repo->objects->sources; source; source = source->next) fsck_source(repo, source); @@ -1155,7 +1154,6 @@ int cmd_fsck(int argc, if (repo->settings.core_commit_graph) { struct child_process commit_graph_verify = CHILD_PROCESS_INIT; - odb_prepare_alternates(repo->objects); for (source = repo->objects->sources; source; source = source->next) { child_process_init(&commit_graph_verify); commit_graph_verify.git_cmd = 1; @@ -1173,7 +1171,6 @@ int cmd_fsck(int argc, if (repo->settings.core_multi_pack_index) { struct child_process midx_verify = CHILD_PROCESS_INIT; - odb_prepare_alternates(repo->objects); for (source = repo->objects->sources; source; source = source->next) { child_process_init(&midx_verify); midx_verify.git_cmd = 1; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 1ec5b6f206..48d37e8e32 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1779,8 +1779,6 @@ static int want_object_in_pack_mtime(const struct object_id *oid, *found_offset = 0; } - odb_prepare_alternates(the_repository->objects); - for (source = the_repository->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); struct multi_pack_index *m = get_multi_pack_index(files->packed); @@ -4520,7 +4518,6 @@ static void add_objects_in_unpacked_packs(void) .source_infop = &source_info, }; - odb_prepare_alternates(to_pack.repo->objects); for (source = to_pack.repo->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); diff --git a/commit-graph.c b/commit-graph.c index 49e8f63930..983c11ce85 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -651,8 +651,6 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb, count = st->st_size / (odb->repo->hash_algo->hexsz + 1); CALLOC_ARRAY(oids, count); - odb_prepare_alternates(odb); - for (i = 0; i < count; i++) { struct odb_source *source; @@ -768,7 +766,6 @@ static struct commit_graph *prepare_commit_graph(struct repository *r) if (!commit_graph_compatible(r)) return NULL; - odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { r->objects->commit_graph = read_commit_graph_one(source); if (r->objects->commit_graph) @@ -2018,7 +2015,6 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx) _("Finding commits for commit graph among packed objects"), ctx->approx_nr_objects); - odb_prepare_alternates(ctx->r->objects); for (source = ctx->r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); odb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi, diff --git a/loose.c b/loose.c index aa3cb1b4fc..c159d29d2d 100644 --- a/loose.c +++ b/loose.c @@ -115,7 +115,6 @@ int repo_read_loose_object_map(struct repository *repo) { struct odb_source *source; - odb_prepare_alternates(repo->objects); for (source = repo->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); if (loose_object_map_load(files->loose) < 0) diff --git a/object-name.c b/object-name.c index 83efba0ba6..34a08d76dd 100644 --- a/object-name.c +++ b/object-name.c @@ -280,7 +280,6 @@ static int init_object_disambiguation(struct repository *r, ds->len = len; ds->repo = r; - odb_prepare_alternates(r->objects); return 0; } diff --git a/odb.c b/odb.c index 22f1425ba5..d4917c3678 100644 --- a/odb.c +++ b/odb.c @@ -252,11 +252,6 @@ void odb_add_to_alternates_file(struct object_database *odb, struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, const char *dir) { - /* - * Make sure alternates are initialized, or else our entry may be - * overwritten when they are. - */ - odb_prepare_alternates(odb); return odb_add_alternate_recursively(odb, dir, 0); } @@ -265,12 +260,6 @@ struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, { struct odb_source *source; - /* - * Make sure alternates are initialized, or else our entry may be - * overwritten when they are. - */ - odb_prepare_alternates(odb); - /* * Make a new primary odb and link the old primary ODB in as an * alternate @@ -376,7 +365,6 @@ struct odb_source *odb_find_source(struct object_database *odb, const char *obj_ char *obj_dir_real = real_pathdup(obj_dir, 1); struct strbuf odb_path_real = STRBUF_INIT; - odb_prepare_alternates(odb); for (source = odb->sources; source; source = source->next) { strbuf_realpath(&odb_path_real, source->path, 1); if (!strcmp(obj_dir_real, odb_path_real.buf)) @@ -510,7 +498,6 @@ int odb_for_each_alternate(struct object_database *odb, struct odb_source *alternate; int r = 0; - odb_prepare_alternates(odb); for (alternate = odb->sources->next; alternate; alternate = alternate->next) { r = cb(alternate, payload); if (r) @@ -519,7 +506,7 @@ int odb_for_each_alternate(struct object_database *odb, return r; } -void odb_prepare_alternates(struct object_database *odb) +static void odb_prepare_alternates(struct object_database *odb) { struct strvec sources = STRVEC_INIT; @@ -538,7 +525,6 @@ void odb_prepare_alternates(struct object_database *odb) int odb_has_alternates(struct object_database *odb) { - odb_prepare_alternates(odb); return !!odb->sources->next; } @@ -598,8 +584,6 @@ static int do_oid_object_info_extended(struct object_database *odb, if (!odb_source_read_object_info(odb->inmemory_objects, oid, oi, flags)) return 0; - odb_prepare_alternates(odb); - while (1) { struct odb_source *source; @@ -862,7 +846,6 @@ int odb_freshen_object(struct object_database *odb, const struct object_id *oid) { struct odb_source *source; - odb_prepare_alternates(odb); for (source = odb->sources; source; source = source->next) if (odb_source_freshen_object(source, oid, NULL)) return 1; @@ -877,7 +860,6 @@ int odb_for_each_object_ext(struct object_database *odb, { int ret; - odb_prepare_alternates(odb); for (struct odb_source *source = odb->sources; source; source = source->next) { if (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local) continue; @@ -915,7 +897,6 @@ int odb_count_objects(struct object_database *odb, return 0; } - odb_prepare_alternates(odb); for (source = odb->sources; source; source = source->next) { unsigned long c; @@ -995,7 +976,6 @@ int odb_find_abbrev_len(struct object_database *odb, goto out; } - odb_prepare_alternates(odb); for (struct odb_source *source = odb->sources; source; source = source->next) { ret = odb_source_find_abbrev_len(source, oid, len, &len); if (ret) @@ -1106,6 +1086,8 @@ struct object_database *odb_new(struct repository *repo, o->alternate_db = secondary_sources; o->inmemory_objects = &odb_source_inmemory_new(o)->base; + odb_prepare_alternates(o); + free(primary_source); return o; } @@ -1166,10 +1148,10 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags) */ if (flags & ODB_PREPARE_FLUSH_CACHES) { o->loaded_alternates = 0; + odb_prepare_alternates(o); o->object_count_valid = 0; } - odb_prepare_alternates(o); for (source = o->sources; source; source = source->next) odb_source_prepare(source, flags); diff --git a/odb.h b/odb.h index 71af7450a9..fbafee174b 100644 --- a/odb.h +++ b/odb.h @@ -273,12 +273,6 @@ void odb_for_each_alternate_ref(struct object_database *odb, int odb_mkstemp(struct object_database *odb, struct strbuf *temp_filename, const char *pattern); -/* - * Prepare alternate object sources for the given database by reading - * "objects/info/alternates" and opening the respective sources. - */ -void odb_prepare_alternates(struct object_database *odb); - /* * Check whether the object database has any alternates. The primary object * source does not count as alternate. diff --git a/odb/streaming.c b/odb/streaming.c index 20531e864c..37642768e9 100644 --- a/odb/streaming.c +++ b/odb/streaming.c @@ -184,7 +184,6 @@ static int istream_source(struct odb_read_stream **out, { struct odb_source *source; - odb_prepare_alternates(odb); for (source = odb->sources; source; source = source->next) if (!odb_source_read_object_stream(out, source, oid)) return 0; diff --git a/pack-bitmap.c b/pack-bitmap.c index e85bd69ba4..e0fb57d332 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -717,7 +717,6 @@ static int open_bitmap(struct repository *r, assert(!bitmap_git->map); - odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); @@ -3417,7 +3416,6 @@ int verify_bitmap_files(struct repository *r) struct packed_git *p; int res = 0; - odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); struct multi_pack_index *m = get_multi_pack_index(files->packed); diff --git a/packfile.c b/packfile.c index 0eee45055f..d870de90ed 100644 --- a/packfile.c +++ b/packfile.c @@ -1938,7 +1938,6 @@ int has_object_pack(struct repository *r, const struct object_id *oid) { struct odb_source *source; - odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); if (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0)) diff --git a/packfile.h b/packfile.h index e1f77152b5..10de24f477 100644 --- a/packfile.h +++ b/packfile.h @@ -77,8 +77,6 @@ static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct { struct repo_for_each_pack_data data = { 0 }; - odb_prepare_alternates(repo->objects); - for (struct odb_source *source = repo->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); struct packfile_list_entry *entry = packfile_store_get_packs(files->packed); From 0e67428c8580c461317768f7fe9916c71435d9e3 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 17 Aug 2026 13:09:24 +0200 Subject: [PATCH 4/5] odb: drop `loaded_alternates` field The `struct object_database::loaded_alternates` field tells us whether or not alternates have been loaded already. This field was useful before the preceding commit as we were indeed lazy-loading alternates. But now that we started to eagerly load them we can assume them to be loaded after `odb_new()`, and hence the field does not serve any purpose anymore. Remove it. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 9 +-------- odb.h | 2 -- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/odb.c b/odb.c index d4917c3678..ada42f864b 100644 --- a/odb.c +++ b/odb.c @@ -245,8 +245,7 @@ void odb_add_to_alternates_file(struct object_database *odb, int ret = odb_source_write_alternate(odb->sources, dir); if (ret < 0) die(NULL); - if (odb->loaded_alternates) - odb_add_alternate_recursively(odb, dir, 0); + odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, @@ -510,16 +509,11 @@ static void odb_prepare_alternates(struct object_database *odb) { struct strvec sources = STRVEC_INIT; - if (odb->loaded_alternates) - return; - parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources); odb_source_read_alternates(odb->sources, &sources); for (size_t i = 0; i < sources.nr; i++) odb_add_alternate_recursively(odb, sources.v[i], 0); - odb->loaded_alternates = 1; - strvec_clear(&sources); } @@ -1147,7 +1141,6 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags) * the lifetime of the process. */ if (flags & ODB_PREPARE_FLUSH_CACHES) { - o->loaded_alternates = 0; odb_prepare_alternates(o); o->object_count_valid = 0; } diff --git a/odb.h b/odb.h index fbafee174b..aefb34213f 100644 --- a/odb.h +++ b/odb.h @@ -69,8 +69,6 @@ struct object_database { */ int source_paths_icase; - int loaded_alternates; - /* * A list of alternate object directories loaded from the environment; * this should not generally need to be accessed directly, but will From 0076dc9f8141bd864e24a4d160b58be7c0597ce7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 17 Aug 2026 13:09:25 +0200 Subject: [PATCH 5/5] odb: drop `alternates_db` field The `struct object_database::alternates_db` field tracks the value of the "GIT_ALTERNATE_OBJECT_DIRECTORIES" environment variable and is used in `odb_prepare_alternates()`. It's not necessary to store it as a separate field anymore though, as we stopped lazy-loading alternates. Consequently, we can simply pass it to `odb_prepare_alternates()` via `odb_new()` now. Do so and remove the field. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 17 +++++++++-------- odb.h | 7 ------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/odb.c b/odb.c index ada42f864b..115957e983 100644 --- a/odb.c +++ b/odb.c @@ -505,12 +505,14 @@ int odb_for_each_alternate(struct object_database *odb, return r; } -static void odb_prepare_alternates(struct object_database *odb) +static void odb_prepare_alternates(struct object_database *odb, + const char *alternate_db) { struct strvec sources = STRVEC_INIT; - parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources); + parse_alternates(alternate_db, PATH_SEP, NULL, &sources); odb_source_read_alternates(odb->sources, &sources); + for (size_t i = 0; i < sources.nr; i++) odb_add_alternate_recursively(odb, sources.v[i], 0); @@ -1077,11 +1079,11 @@ struct object_database *odb_new(struct repository *repo, o->sources = odb_source_new(o, primary_source, true); o->sources_tail = &o->sources->next; - o->alternate_db = secondary_sources; o->inmemory_objects = &odb_source_inmemory_new(o)->base; - odb_prepare_alternates(o); + odb_prepare_alternates(o, secondary_sources); + free(secondary_sources); free(primary_source); return o; } @@ -1115,8 +1117,6 @@ void odb_free(struct object_database *o) if (!o) return; - free(o->alternate_db); - oidmap_clear(&o->replace_map, 1); pthread_mutex_destroy(&o->replace_mutex); @@ -1138,10 +1138,11 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags) * Reprepare alt odbs, in case the alternates file was modified * during the course of this process. This only _adds_ odbs to * the linked list, so existing odbs will continue to exist for - * the lifetime of the process. + * the lifetime of the process. Consequently, we don't have to + * reprocess GIT_ALTERNATE_OBJECT_DIRECTORIES here. */ if (flags & ODB_PREPARE_FLUSH_CACHES) { - odb_prepare_alternates(o); + odb_prepare_alternates(o, NULL); o->object_count_valid = 0; } diff --git a/odb.h b/odb.h index aefb34213f..748366a610 100644 --- a/odb.h +++ b/odb.h @@ -69,13 +69,6 @@ struct object_database { */ int source_paths_icase; - /* - * A list of alternate object directories loaded from the environment; - * this should not generally need to be accessed directly, but will - * populate the "sources" list when odb_prepare_alternates() is run. - */ - char *alternate_db; - /* * Objects that should be substituted by other objects * (see git-replace(1)).