Merge branch 'ps/odb-eagerly-load-alternates'

The object database layer has been simplified by eagerly loading
alternate object directories upon initialization, instead of
deferring it to the first object lookup.  This eliminates the need
for scattered lazy-loading calls throughout the codebase and paves
the way for integrating alternates with the pluggable backends.

* ps/odb-eagerly-load-alternates:
  odb: drop `alternates_db` field
  odb: drop `loaded_alternates` field
  odb: eagerly initialize alternates
  odb: decouple source path comparisons from `the_repository`
  setup: create ref and object databases after config is written
main
Junio C Hamano 2026-08-31 08:24:59 -07:00
commit 8b92a9cf4f
13 changed files with 92 additions and 93 deletions

View File

@ -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;

View File

@ -1780,8 +1780,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);


View File

@ -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,

View File

@ -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)

View File

@ -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;
}


124
odb.c
View File

@ -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"
@ -28,8 +27,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)
@ -57,8 +95,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);
@ -75,20 +113,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;
@ -171,8 +207,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;
@ -183,10 +217,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);
@ -209,18 +244,12 @@ 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,
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);
}

@ -229,12 +258,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
@ -340,7 +363,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))
@ -474,7 +496,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)
@ -483,26 +504,22 @@ 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,
const char *alternate_db)
{
struct strvec sources = STRVEC_INIT;

if (odb->loaded_alternates)
return;

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);

odb->loaded_alternates = 1;

strvec_clear(&sources);
}

int odb_has_alternates(struct object_database *odb)
{
odb_prepare_alternates(odb);
return !!odb->sources->next;
}

@ -565,8 +582,6 @@ static enum odb_read_status do_oid_object_info_extended(struct object_database *
if (!odb_source_read_object_info(odb->inmemory_objects, oid, oi, flags, NULL))
return 0;

odb_prepare_alternates(odb);

while (1) {
struct odb_source *source;

@ -846,7 +861,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;
@ -861,7 +875,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;
@ -899,7 +912,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;

@ -979,7 +991,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)
@ -1075,6 +1086,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));
@ -1085,9 +1098,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, secondary_sources);

free(secondary_sources);
free(primary_source);
return o;
}
@ -1113,8 +1128,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)
@ -1122,8 +1136,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);

@ -1145,14 +1157,14 @@ 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) {
o->loaded_alternates = 0;
odb_prepare_alternates(o, NULL);
o->object_count_valid = 0;
}

odb_prepare_alternates(o);
for (source = o->sources; source; source = source->next)
odb_source_prepare(source, flags);


24
odb.h
View File

@ -1,6 +1,7 @@
#ifndef ODB_H
#define ODB_H

#include "hashmap.h"
#include "object.h"
#include "oidset.h"
#include "oidmap.h"
@ -54,16 +55,19 @@ struct object_database {
*/
struct odb_source *sources;
struct odb_source **sources_tail;
struct kh_odb_path_map *source_by_path;

int loaded_alternates;

/*
* 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.
* 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.
*/
char *alternate_db;
struct hashmap source_by_path;

/*
* Whether source paths shall be compared case-insensitively, as
* determined by "core.ignoreCase".
*/
int source_paths_icase;

/*
* Objects that should be substituted by other objects
@ -260,12 +264,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.

View File

@ -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"
@ -51,6 +52,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;


View File

@ -184,7 +184,6 @@ static int istream_source(struct odb_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;

View File

@ -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);

View File

@ -1923,7 +1923,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, NULL))

View File

@ -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);

12
setup.c
View File

@ -2879,12 +2879,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
@ -2906,6 +2900,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);