Merge branch 'ps/odb-make-creation-pluggable' into ps/odb-eagerly-load-alternates

* ps/odb-make-creation-pluggable:
  odb: make creation of on-disk structures pluggable
  odb/source: introduce function to map source type to name
  setup: defer object database creation
  setup: handle ODB-related environment variables in `odb_new()`
  setup: detangle loading of loose object maps
  loose: load loose object map for the correct source
main
Junio C Hamano 2026-08-12 08:33:36 -07:00
commit d296c52baa
17 changed files with 168 additions and 58 deletions

25
loose.c
View File

@ -61,12 +61,17 @@ static int insert_loose_map(struct odb_source_loose *loose,
return inserted;
}

static int load_one_loose_object_map(struct repository *repo, struct odb_source_loose *loose)
int loose_object_map_load(struct odb_source_loose *loose)
{
struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
struct repository *repo = loose->base.odb->repo;
struct strbuf buf = STRBUF_INIT;
char *path;
FILE *fp;
int ret = -1;

if (!should_use_loose_object_map(repo))
return 0;

if (!loose->map)
loose_object_map_init(&loose->map);
if (!loose->cache) {
@ -78,10 +83,10 @@ static int load_one_loose_object_map(struct repository *repo, struct odb_source_
insert_loose_map(loose, repo->hash_algo->empty_blob, repo->compat_hash_algo->empty_blob);
insert_loose_map(loose, repo->hash_algo->null_oid, repo->compat_hash_algo->null_oid);

repo_common_path_replace(repo, &path, "objects/loose-object-idx");
fp = fopen(path.buf, "rb");
path = xstrfmt("%s/loose-object-idx", loose->base.path);
fp = fopen(path, "rb");
if (!fp) {
strbuf_release(&path);
free(path);
return 0;
}

@ -102,7 +107,7 @@ static int load_one_loose_object_map(struct repository *repo, struct odb_source_
err:
fclose(fp);
strbuf_release(&buf);
strbuf_release(&path);
free(path);
return ret;
}

@ -110,17 +115,13 @@ int repo_read_loose_object_map(struct repository *repo)
{
struct odb_source *source;

if (!should_use_loose_object_map(repo))
return 0;

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 (load_one_loose_object_map(repo, files->loose) < 0) {
if (loose_object_map_load(files->loose) < 0)
return -1;
}
}

return 0;
}


View File

@ -13,6 +13,7 @@ struct loose_object_map {

void loose_object_map_init(struct loose_object_map **map);
void loose_object_map_clear(struct loose_object_map **map);
int loose_object_map_load(struct odb_source_loose *loose);
int repo_loose_object_map_oid(struct repository *repo,
const struct object_id *src,
const struct git_hash_algo *dest_algo,

21
odb.c
View File

@ -1047,26 +1047,29 @@ bool odb_optimize_required(struct object_database *odb,
}

struct object_database *odb_new(struct repository *repo,
const char *primary_source,
const char *secondary_sources)
enum odb_new_flags flags)
{
struct object_database *o = xmalloc(sizeof(*o));
char *to_free = NULL;
char *primary_source = NULL, *secondary_sources = NULL;
struct object_database *o;

memset(o, 0, sizeof(*o));
CALLOC_ARRAY(o, 1);
o->repo = repo;
pthread_mutex_init(&o->replace_mutex, NULL);
string_list_init_dup(&o->submodule_source_paths);

if (flags & ODB_NEW_HONOR_ENV) {
primary_source = xstrdup_or_null(getenv(DB_ENVIRONMENT));
secondary_sources = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
}
if (!primary_source)
primary_source = to_free = xstrfmt("%s/objects", repo->commondir);
primary_source = xstrfmt("%s/objects", repo->commondir);

o->sources = odb_source_new(o, primary_source, true);
o->sources_tail = &o->sources->next;
o->alternate_db = xstrdup_or_null(secondary_sources);
o->alternate_db = secondary_sources;
o->inmemory_objects = &odb_source_inmemory_new(o)->base;

free(to_free);

free(primary_source);
return o;
}


17
odb.h
View File

@ -100,6 +100,20 @@ struct object_database {
struct string_list submodule_source_paths;
};

enum odb_new_flags {
/*
* Honor environment variables when constructing the object database
* sources. This makes us respect the following environment variables:
*
* - GIT_OBJECT_DIRECTORY to override the primary object directory.
*
* - GIT_ALTERNATE_OBJECT_DIRECTORIES to override alternates.
*
* Environment variables may be backend-specific.
*/
ODB_NEW_HONOR_ENV = (1 << 0),
};

/*
* Create a new object database for the given repository.
*
@ -112,8 +126,7 @@ struct object_database {
* Returns the newly created object database.
*/
struct object_database *odb_new(struct repository *repo,
const char *primary_source,
const char *alternate_sources);
enum odb_new_flags flags);

/* Free the object database and release all resources. */
void odb_free(struct object_database *o);

View File

@ -51,6 +51,23 @@ static void odb_source_files_close(struct odb_source *source)
odb_source_close(&files->packed->base);
}

static int odb_source_files_create_on_disk(struct odb_source *source)
{
struct strbuf path = STRBUF_INIT;

safe_create_dir(source->odb->repo, source->path, 1);

strbuf_addf(&path, "%s/pack", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);

strbuf_reset(&path);
strbuf_addf(&path, "%s/info", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);

strbuf_release(&path);
return 0;
}

static void odb_source_files_prepare(struct odb_source *source,
enum odb_prepare_flags flags)
{
@ -742,6 +759,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,

files->base.free = odb_source_files_free;
files->base.close = odb_source_files_close;
files->base.create_on_disk = odb_source_files_create_on_disk;
files->base.prepare = odb_source_files_prepare;
files->base.read_object_info = odb_source_files_read_object_info;
files->base.read_object_stream = odb_source_files_read_object_stream;

View File

@ -43,7 +43,9 @@ bool odb_source_files_optimize_required(struct odb_source *source,
static inline struct odb_source_files *odb_source_files_downcast(struct odb_source *source)
{
if (source->type != ODB_SOURCE_FILES)
BUG("trying to downcast source of type '%d' to files", source->type);
BUG("trying to downcast source of type '%s' to '%s'",
odb_source_type_to_name(source->type),
odb_source_type_to_name(ODB_SOURCE_FILES));
return container_of(source, struct odb_source_files, base);
}


View File

@ -26,7 +26,9 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
static inline struct odb_source_inmemory *odb_source_inmemory_downcast(struct odb_source *source)
{
if (source->type != ODB_SOURCE_INMEMORY)
BUG("trying to downcast source of type '%d' to in-memory", source->type);
BUG("trying to downcast source of type '%s' to '%s'",
odb_source_type_to_name(source->type),
odb_source_type_to_name(ODB_SOURCE_INMEMORY));
return container_of(source, struct odb_source_inmemory, base);
}


View File

@ -1055,5 +1055,7 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);

loose_object_map_load(loose);

return loose;
}

View File

@ -41,7 +41,9 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
static inline struct odb_source_loose *odb_source_loose_downcast(struct odb_source *source)
{
if (source->type != ODB_SOURCE_LOOSE)
BUG("trying to downcast source of type '%d' to loose", source->type);
BUG("trying to downcast source of type '%s' to '%s'",
odb_source_type_to_name(source->type),
odb_source_type_to_name(ODB_SOURCE_LOOSE));
return container_of(source, struct odb_source_loose, base);
}


View File

@ -78,7 +78,9 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_source *source)
{
if (source->type != ODB_SOURCE_PACKED)
BUG("trying to downcast source of type '%d' to packed", source->type);
BUG("trying to downcast source of type '%s' to '%s'",
odb_source_type_to_name(source->type),
odb_source_type_to_name(ODB_SOURCE_PACKED));
return container_of(source, struct odb_source_packed, base);
}


View File

@ -4,6 +4,25 @@
#include "odb/source.h"
#include "packfile.h"

static const char * const odb_source_names_by_type[] = {
[ODB_SOURCE_UNKNOWN] = "unknown",
[ODB_SOURCE_FILES] = "files",
[ODB_SOURCE_LOOSE] = "loose",
[ODB_SOURCE_PACKED] = "packed",
[ODB_SOURCE_INMEMORY] = "in-memory",
};

const char *odb_source_type_to_name(enum odb_source_type type)
{
const char *name;
if (type < 0 || type >= ARRAY_SIZE(odb_source_names_by_type))
type = ODB_SOURCE_UNKNOWN;
name = odb_source_names_by_type[type];
if (!name)
BUG("name missing in `odb_source_names_by_type` for '%d'", type);
return name;
}

struct odb_source *odb_source_new(struct object_database *odb,
const char *path,
bool local)

View File

@ -25,6 +25,12 @@ enum odb_source_type {
ODB_SOURCE_INMEMORY,
};

/*
* Convert between the enum and its name. Returns the equivalent of "unknown"
* for unknown types.
*/
const char *odb_source_type_to_name(enum odb_source_type type);

struct object_id;
struct odb_read_stream;
struct strvec;
@ -83,6 +89,18 @@ struct odb_source {
*/
void (*close)(struct odb_source *source);

/*
* This callback is expected to create on-disk data structures that are
* required for this source to operate.
*
* The callback is expected to return 0 on success, a negative error
* code otherwise.
*
* This callback may be NULL in case the source does not need any
* on-disk setup.
*/
int (*create_on_disk)(struct odb_source *source);

/*
* This callback is expected to prepare the source so that it becomes
* ready for use. It optionally clears underlying caches of the object
@ -327,6 +345,17 @@ static inline void odb_source_close(struct odb_source *source)
source->close(source);
}

/*
* Create on-disk data structures that are required for this source to operate
* correctly. Returns 0 on success, a negative error code otherwise.
*/
static inline int odb_source_create_on_disk(struct odb_source *source)
{
if (!source->create_on_disk)
return 0;
return source->create_on_disk(source);
}

/*
* Prepare the object database source and clear any caches. Depending on the
* backend used this may have the effect that concurrently-written objects

View File

@ -201,8 +201,6 @@ void repo_set_compat_hash_algo(struct repository *repo MAYBE_UNUSED, uint32_t al
if (hash_algo_by_ptr(repo->hash_algo) == algo)
BUG("hash_algo and compat_hash_algo match");
repo->compat_hash_algo = algo ? &hash_algos[algo] : NULL;
if (repo->compat_hash_algo)
repo_read_loose_object_map(repo);
#else
if (algo)
die(_("compatibility hash algorithm support requires Rust"));
@ -296,6 +294,7 @@ int repo_init(struct repository *repo,
warning("%s", err.buf);
goto error;
}
repo->objects = odb_new(repo, 0);

if (worktree)
repo_set_worktree(repo, worktree);

51
setup.c
View File

@ -1765,8 +1765,6 @@ int apply_repository_format(struct repository *repo,
enum apply_repository_format_flags flags,
struct strbuf *err)
{
char *object_directory = NULL, *alternate_object_directories = NULL;

if (verify_repository_format(format, err) < 0)
return -1;

@ -1779,8 +1777,6 @@ int apply_repository_format(struct repository *repo,
if (flags & APPLY_REPOSITORY_FORMAT_HONOR_ENV) {
const char *shallow_file;

object_directory = xstrdup_or_null(getenv(DB_ENVIRONMENT));
alternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
if (shallow_file)
set_alternate_shallow_file(repo, shallow_file);
@ -1788,8 +1784,6 @@ int apply_repository_format(struct repository *repo,

repo->bare_cfg = format->is_bare;
repo_set_hash_algo(repo, format->hash_algo);
repo->objects = odb_new(repo, object_directory,
alternate_object_directories);
repo_set_compat_hash_algo(repo, format->compat_hash_algo);
repo_set_ref_storage_format(repo,
format->ref_storage_format,
@ -1805,8 +1799,6 @@ int apply_repository_format(struct repository *repo,
repo->repository_format_precious_objects =
format->precious_objects;

free(alternate_object_directories);
free(object_directory);
return 0;
}

@ -1890,6 +1882,7 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags
read_and_verify_repository_format(&fmt, ".", NULL);
if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
die("%s", err.buf);
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
startup_info->have_repository = 1;

clear_repository_format(&fmt);
@ -2092,6 +2085,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
if (apply_repository_format(repo, &discovery.format,
APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
die("%s", err.buf);
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);

clear_repository_format(&discovery.format);
strbuf_release(&err);
@ -2653,25 +2647,29 @@ static int create_default_files(struct repository *repo,
return reinit;
}

static void create_object_directory(struct repository *repo)
static void create_object_database(struct repository *repo)
{
struct strbuf path = STRBUF_INIT;
size_t baselen;
/*
* Create the "objects" directory in the common directory. This is done
* so that the repository can be discovered regardless of the backend
* used.
*
* Note that we only do this in case the object directory wasn't
* overwritten via an environment variable. If it _is_ being overridden
* then we skip this step, as the repository won't be discoverable
* anyway without the environment variable.
*/
if (!getenv(DB_ENVIRONMENT)) {
struct strbuf objects_dir = STRBUF_INIT;
repo_common_path_append(repo, &objects_dir, "objects");
safe_create_dir(repo, objects_dir.buf, 1);
strbuf_release(&objects_dir);
}

strbuf_addstr(&path, repo_get_object_directory(repo));
baselen = path.len;
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);

safe_create_dir(repo, path.buf, 1);

strbuf_setlen(&path, baselen);
strbuf_addstr(&path, "/pack");
safe_create_dir(repo, path.buf, 1);

strbuf_setlen(&path, baselen);
strbuf_addstr(&path, "/info");
safe_create_dir(repo, path.buf, 1);

strbuf_release(&path);
if (odb_source_create_on_disk(repo->objects->sources) < 0)
die(_("failed creating object database"));
}

static void separate_git_dir(struct repository *repo,
@ -2869,7 +2867,6 @@ int init_db(struct repository *repo,
repository_format_configure(&repo_fmt, hash, ref_storage_format);
if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
die("%s", err.buf);
startup_info->have_repository = 1;

/*
* Ensure `core.hidedotfiles` is processed. This must happen after we
@ -2885,7 +2882,9 @@ int init_db(struct repository *repo,

if (!(flags & INIT_DB_SKIP_REFDB))
create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
create_object_directory(repo);
create_object_database(repo);

startup_info->have_repository = 1;

if (repo_settings_get_shared_repository(repo)) {
char buf[10];

View File

@ -245,8 +245,8 @@ enum apply_repository_format_flags {

/*
* Apply the given repository format to the repo. This initializes extensions
* and basic data structures required for normal operation. Returns 0 on
* success, a negative error code when the format is not valid as determined by
* required for normal operation. Returns 0 on success, a negative error code
* when the format is not valid as determined by
* `verify_repository_format()`.
*/
int apply_repository_format(struct repository *repo,

View File

@ -187,6 +187,24 @@ do
eval signedtag3_${hash}_oid=$(git hash-object -t tag -w ../${hash}_signedtag3) &&
eval signedtag4_${hash}_oid=$(git hash-object -t tag -w ../${hash}_signedtag4)
'

test_expect_success 'rev-parse maps oid of object borrowed from alternate' '
for repo in alt borrow
do
test_when_finished "rm -rf $repo" &&
git init --object-format=$hash $repo &&
git -C $repo config set core.repositoryformatversion 1 &&
git -C $repo config set extensions.compatObjectFormat $(compat_hash $hash) || exit 1
done &&

git -C alt commit --allow-empty --message A &&
echo "$(pwd)/alt/.git/objects" >borrow/.git/objects/info/alternates &&

oid=$(git -C alt rev-parse HEAD) &&
git -C alt rev-parse --output-object-format=$(compat_hash $hash) "$oid" >expect &&
git -C borrow rev-parse --output-object-format=$(compat_hash $hash) "$oid" >actual &&
test_cmp expect actual
'
done
cd "$base"


View File

@ -50,7 +50,7 @@ static void cl_assert_write_object(struct odb_source_inmemory *source,

void test_odb_inmemory__initialize(void)
{
odb = odb_new(&repo, "", "");
odb = odb_new(&repo, 0);
}

void test_odb_inmemory__cleanup(void)