From 585e8dfa27050ce0a69c6e7ead0a3355912d4992 Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Mon, 2 Feb 2026 18:09:59 -0600 Subject: [PATCH 1/4] odb: store ODB source in `struct odb_transaction` Each `struct odb_transaction` currently stores a reference to the `struct object_database`. Since transactions are handled per object source, instead store a reference to the source. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- object-file.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/object-file.c b/object-file.c index e7e4c3348f..196509b252 100644 --- a/object-file.c +++ b/object-file.c @@ -711,7 +711,7 @@ struct transaction_packfile { }; struct odb_transaction { - struct object_database *odb; + struct odb_source *source; struct tmp_objdir *objdir; struct transaction_packfile packfile; @@ -728,7 +728,7 @@ static void prepare_loose_object_transaction(struct odb_transaction *transaction if (!transaction || transaction->objdir) return; - transaction->objdir = tmp_objdir_create(transaction->odb->repo, "bulk-fsync"); + transaction->objdir = tmp_objdir_create(transaction->source->odb->repo, "bulk-fsync"); if (transaction->objdir) tmp_objdir_replace_primary_odb(transaction->objdir, 0); } @@ -772,7 +772,7 @@ static void flush_loose_object_transaction(struct odb_transaction *transaction) * the final name is visible. */ strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", - repo_get_object_directory(transaction->odb->repo)); + repo_get_object_directory(transaction->source->odb->repo)); temp = xmks_tempfile(temp_path.buf); fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp)); delete_tempfile(&temp); @@ -1344,7 +1344,7 @@ static int already_written(struct odb_transaction *transaction, struct object_id *oid) { /* The object may already exist in the repository */ - if (odb_has_object(transaction->odb, oid, + if (odb_has_object(transaction->source->odb, oid, HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) return 1; @@ -1365,7 +1365,7 @@ static void prepare_packfile_transaction(struct odb_transaction *transaction, if (!(flags & INDEX_WRITE_OBJECT) || state->f) return; - state->f = create_tmp_packfile(transaction->odb->repo, + state->f = create_tmp_packfile(transaction->source->odb->repo, &state->pack_tmp_name); reset_pack_idx_option(&state->pack_idx_opts); @@ -1469,7 +1469,7 @@ static int stream_blob_to_pack(struct transaction_packfile *state, static void flush_packfile_transaction(struct odb_transaction *transaction) { struct transaction_packfile *state = &transaction->packfile; - struct repository *repo = transaction->odb->repo; + struct repository *repo = transaction->source->odb->repo; unsigned char hash[GIT_MAX_RAWSZ]; struct strbuf packname = STRBUF_INIT; char *idx_tmp_name = NULL; @@ -1494,7 +1494,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction) } strbuf_addf(&packname, "%s/pack/pack-%s.", - repo_get_object_directory(transaction->odb->repo), + repo_get_object_directory(transaction->source->odb->repo), hash_to_hex_algop(hash, repo->hash_algo)); stage_tmp_packfiles(repo, &packname, state->pack_tmp_name, @@ -1553,7 +1553,7 @@ static int index_blob_packfile_transaction(struct odb_transaction *transaction, header_len = format_object_header((char *)obuf, sizeof(obuf), OBJ_BLOB, size); - transaction->odb->repo->hash_algo->init_fn(&ctx); + transaction->source->odb->repo->hash_algo->init_fn(&ctx); git_hash_update(&ctx, obuf, header_len); /* Note: idx is non-NULL when we are writing */ @@ -1993,7 +1993,7 @@ struct odb_transaction *object_file_transaction_begin(struct odb_source *source) return NULL; CALLOC_ARRAY(odb->transaction, 1); - odb->transaction->odb = odb; + odb->transaction->source = source; return odb->transaction; } @@ -2006,11 +2006,11 @@ void object_file_transaction_commit(struct odb_transaction *transaction) /* * Ensure the transaction ending matches the pending transaction. */ - ASSERT(transaction == transaction->odb->transaction); + ASSERT(transaction == transaction->source->odb->transaction); flush_loose_object_transaction(transaction); flush_packfile_transaction(transaction); - transaction->odb->transaction = NULL; + transaction->source->odb->transaction = NULL; free(transaction); } From 8bf06d05a581dfb552cd0e290680f75d3676eb1d Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Mon, 2 Feb 2026 18:10:00 -0600 Subject: [PATCH 2/4] object-file: rename transaction functions In a subsequent commit, ODB transactions are made more generic to facilitate each ODB source providing its own transaction handling. Rename `object_file_transaction_{begin,commit}()` to `odb_transaction_files_{begin,commit}()` to better match the future source specific transaction implementation. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- object-file.c | 6 +++--- object-file.h | 6 +++--- odb.c | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/object-file.c b/object-file.c index 196509b252..7b34a2b274 100644 --- a/object-file.c +++ b/object-file.c @@ -723,7 +723,7 @@ static void prepare_loose_object_transaction(struct odb_transaction *transaction * We lazily create the temporary object directory * the first time an object might be added, since * callers may not know whether any objects will be - * added at the time they call object_file_transaction_begin. + * added at the time they call odb_transaction_files_begin. */ if (!transaction || transaction->objdir) return; @@ -1985,7 +1985,7 @@ out: return ret; } -struct odb_transaction *object_file_transaction_begin(struct odb_source *source) +struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) { struct object_database *odb = source->odb; @@ -1998,7 +1998,7 @@ struct odb_transaction *object_file_transaction_begin(struct odb_source *source) return odb->transaction; } -void object_file_transaction_commit(struct odb_transaction *transaction) +void odb_transaction_files_commit(struct odb_transaction *transaction) { if (!transaction) return; diff --git a/object-file.h b/object-file.h index 1229d5f675..b4a3341a89 100644 --- a/object-file.h +++ b/object-file.h @@ -202,16 +202,16 @@ struct odb_transaction; /* * Tell the object database to optimize for adding - * multiple objects. object_file_transaction_commit must be called + * multiple objects. odb_transaction_files_commit must be called * to make new objects visible. If a transaction is already * pending, NULL is returned. */ -struct odb_transaction *object_file_transaction_begin(struct odb_source *source); +struct odb_transaction *odb_transaction_files_begin(struct odb_source *source); /* * Tell the object database to make any objects from the * current transaction visible. */ -void object_file_transaction_commit(struct odb_transaction *transaction); +void odb_transaction_files_commit(struct odb_transaction *transaction); #endif /* OBJECT_FILE_H */ diff --git a/odb.c b/odb.c index ac70b6a099..a5e6fd01a9 100644 --- a/odb.c +++ b/odb.c @@ -1153,10 +1153,10 @@ void odb_reprepare(struct object_database *o) struct odb_transaction *odb_transaction_begin(struct object_database *odb) { - return object_file_transaction_begin(odb->sources); + return odb_transaction_files_begin(odb->sources); } void odb_transaction_commit(struct odb_transaction *transaction) { - object_file_transaction_commit(transaction); + odb_transaction_files_commit(transaction); } From fa7d067923a342c298b7723935c60217a5244e4e Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Mon, 2 Feb 2026 18:10:01 -0600 Subject: [PATCH 3/4] odb: prepare `struct odb_transaction` to become generic An ODB transaction handles how objects are stored temporarily and eventually committed. Due to object storage being implemented differently for a given ODB source, the ODB transactions must be implemented in a manner specific to the source the objects are being written to. To provide generic transactions, `struct odb_transaction` is updated to store a commit callback that can be configured to support a specific ODB source. For now `struct odb_transaction_files` is the only transaction type and what is always returned when starting a transaction. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- object-file.c | 80 ++++++++++++++++++++++++++++----------------------- object-file.h | 6 ---- odb.c | 5 +++- odb.h | 17 +++++++++++ 4 files changed, 65 insertions(+), 43 deletions(-) diff --git a/object-file.c b/object-file.c index 7b34a2b274..d7e153c1b9 100644 --- a/object-file.c +++ b/object-file.c @@ -710,15 +710,17 @@ struct transaction_packfile { uint32_t nr_written; }; -struct odb_transaction { - struct odb_source *source; +struct odb_transaction_files { + struct odb_transaction base; struct tmp_objdir *objdir; struct transaction_packfile packfile; }; -static void prepare_loose_object_transaction(struct odb_transaction *transaction) +static void prepare_loose_object_transaction(struct odb_transaction *base) { + struct odb_transaction_files *transaction = (struct odb_transaction_files *)base; + /* * We lazily create the temporary object directory * the first time an object might be added, since @@ -728,14 +730,16 @@ static void prepare_loose_object_transaction(struct odb_transaction *transaction if (!transaction || transaction->objdir) return; - transaction->objdir = tmp_objdir_create(transaction->source->odb->repo, "bulk-fsync"); + transaction->objdir = tmp_objdir_create(base->source->odb->repo, "bulk-fsync"); if (transaction->objdir) tmp_objdir_replace_primary_odb(transaction->objdir, 0); } -static void fsync_loose_object_transaction(struct odb_transaction *transaction, +static void fsync_loose_object_transaction(struct odb_transaction *base, int fd, const char *filename) { + struct odb_transaction_files *transaction = (struct odb_transaction_files *)base; + /* * If we have an active ODB transaction, we issue a call that * cleans the filesystem page cache but avoids a hardware flush @@ -754,7 +758,7 @@ static void fsync_loose_object_transaction(struct odb_transaction *transaction, /* * Cleanup after batch-mode fsync_object_files. */ -static void flush_loose_object_transaction(struct odb_transaction *transaction) +static void flush_loose_object_transaction(struct odb_transaction_files *transaction) { struct strbuf temp_path = STRBUF_INIT; struct tempfile *temp; @@ -772,7 +776,7 @@ static void flush_loose_object_transaction(struct odb_transaction *transaction) * the final name is visible. */ strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", - repo_get_object_directory(transaction->source->odb->repo)); + repo_get_object_directory(transaction->base.source->odb->repo)); temp = xmks_tempfile(temp_path.buf); fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp)); delete_tempfile(&temp); @@ -1340,11 +1344,11 @@ static int index_core(struct index_state *istate, return ret; } -static int already_written(struct odb_transaction *transaction, +static int already_written(struct odb_transaction_files *transaction, struct object_id *oid) { /* The object may already exist in the repository */ - if (odb_has_object(transaction->source->odb, oid, + if (odb_has_object(transaction->base.source->odb, oid, HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) return 1; @@ -1358,14 +1362,14 @@ static int already_written(struct odb_transaction *transaction, } /* Lazily create backing packfile for the state */ -static void prepare_packfile_transaction(struct odb_transaction *transaction, +static void prepare_packfile_transaction(struct odb_transaction_files *transaction, unsigned flags) { struct transaction_packfile *state = &transaction->packfile; if (!(flags & INDEX_WRITE_OBJECT) || state->f) return; - state->f = create_tmp_packfile(transaction->source->odb->repo, + state->f = create_tmp_packfile(transaction->base.source->odb->repo, &state->pack_tmp_name); reset_pack_idx_option(&state->pack_idx_opts); @@ -1466,10 +1470,10 @@ static int stream_blob_to_pack(struct transaction_packfile *state, return 0; } -static void flush_packfile_transaction(struct odb_transaction *transaction) +static void flush_packfile_transaction(struct odb_transaction_files *transaction) { struct transaction_packfile *state = &transaction->packfile; - struct repository *repo = transaction->source->odb->repo; + struct repository *repo = transaction->base.source->odb->repo; unsigned char hash[GIT_MAX_RAWSZ]; struct strbuf packname = STRBUF_INIT; char *idx_tmp_name = NULL; @@ -1494,7 +1498,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction) } strbuf_addf(&packname, "%s/pack/pack-%s.", - repo_get_object_directory(transaction->source->odb->repo), + repo_get_object_directory(transaction->base.source->odb->repo), hash_to_hex_algop(hash, repo->hash_algo)); stage_tmp_packfiles(repo, &packname, state->pack_tmp_name, @@ -1534,7 +1538,7 @@ clear_exit: * binary blobs, they generally do not want to get any conversion, and * callers should avoid this code path when filters are requested. */ -static int index_blob_packfile_transaction(struct odb_transaction *transaction, +static int index_blob_packfile_transaction(struct odb_transaction_files *transaction, struct object_id *result_oid, int fd, size_t size, const char *path, unsigned flags) @@ -1553,7 +1557,7 @@ static int index_blob_packfile_transaction(struct odb_transaction *transaction, header_len = format_object_header((char *)obuf, sizeof(obuf), OBJ_BLOB, size); - transaction->source->odb->repo->hash_algo->init_fn(&ctx); + transaction->base.source->odb->repo->hash_algo->init_fn(&ctx); git_hash_update(&ctx, obuf, header_len); /* Note: idx is non-NULL when we are writing */ @@ -1629,10 +1633,11 @@ int index_fd(struct index_state *istate, struct object_id *oid, ret = index_core(istate, oid, fd, xsize_t(st->st_size), type, path, flags); } else { + struct object_database *odb = the_repository->objects; struct odb_transaction *transaction; - transaction = odb_transaction_begin(the_repository->objects); - ret = index_blob_packfile_transaction(the_repository->objects->transaction, + transaction = odb_transaction_begin(odb); + ret = index_blob_packfile_transaction((struct odb_transaction_files *)odb->transaction, oid, fd, xsize_t(st->st_size), path, flags); @@ -1985,33 +1990,36 @@ out: return ret; } +static void odb_transaction_files_commit(struct odb_transaction *base) +{ + struct odb_transaction_files *transaction = (struct odb_transaction_files *)base; + + /* + * Ensure the transaction ending matches the pending transaction. + */ + ASSERT(base == base->source->odb->transaction); + + flush_loose_object_transaction(transaction); + flush_packfile_transaction(transaction); + base->source->odb->transaction = NULL; + free(transaction); +} + struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) { + struct odb_transaction_files *transaction; struct object_database *odb = source->odb; if (odb->transaction) return NULL; - CALLOC_ARRAY(odb->transaction, 1); - odb->transaction->source = source; + transaction = xcalloc(1, sizeof(*transaction)); + transaction->base.source = source; + transaction->base.commit = odb_transaction_files_commit; - return odb->transaction; -} + odb->transaction = &transaction->base; -void odb_transaction_files_commit(struct odb_transaction *transaction) -{ - if (!transaction) - return; - - /* - * Ensure the transaction ending matches the pending transaction. - */ - ASSERT(transaction == transaction->source->odb->transaction); - - flush_loose_object_transaction(transaction); - flush_packfile_transaction(transaction); - transaction->source->odb->transaction = NULL; - free(transaction); + return &transaction->base; } struct odb_source_loose *odb_source_loose_new(struct odb_source *source) diff --git a/object-file.h b/object-file.h index b4a3341a89..a62d0de394 100644 --- a/object-file.h +++ b/object-file.h @@ -208,10 +208,4 @@ struct odb_transaction; */ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source); -/* - * Tell the object database to make any objects from the - * current transaction visible. - */ -void odb_transaction_files_commit(struct odb_transaction *transaction); - #endif /* OBJECT_FILE_H */ diff --git a/odb.c b/odb.c index a5e6fd01a9..349b4218a5 100644 --- a/odb.c +++ b/odb.c @@ -1158,5 +1158,8 @@ struct odb_transaction *odb_transaction_begin(struct object_database *odb) void odb_transaction_commit(struct odb_transaction *transaction) { - odb_transaction_files_commit(transaction); + if (!transaction) + return; + + transaction->commit(transaction); } diff --git a/odb.h b/odb.h index bab07755f4..83d3a37805 100644 --- a/odb.h +++ b/odb.h @@ -77,7 +77,24 @@ struct odb_source { struct packed_git; struct packfile_store; struct cached_object_entry; + +/* + * A transaction may be started for an object database prior to writing new + * objects via odb_transaction_begin(). These objects are not committed until + * odb_transaction_commit() is invoked. Only a single transaction may be pending + * at a time. + * + * Each ODB source is expected to implement its own transaction handling. + */ struct odb_transaction; +typedef void (*odb_transaction_commit_fn)(struct odb_transaction *transaction); +struct odb_transaction { + /* The ODB source the transaction is opened against. */ + struct odb_source *source; + + /* The ODB source specific callback invoked to commit a transaction. */ + odb_transaction_commit_fn commit; +}; /* * The object database encapsulates access to objects in a repository. It From 3f67e3d0211dd06d27ee3ee5b23e5f328ff2db12 Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Mon, 2 Feb 2026 18:10:02 -0600 Subject: [PATCH 4/4] odb: transparently handle common transaction behavior A new ODB transaction is created and returned via `odb_transaction_begin()` and stored in the ODB. Only a single transaction may be pending at a time. If the ODB already has a transaction, the function is expected to return NULL. Similarly, when committing a transaction via `odb_transaction_commit()` the transaction being committed must match the pending transaction and upon commit reset the ODB transaction to NULL. These behaviors apply regardless of the ODB transaction implementation. Move the corresponding logic into `odb_transaction_{begin,commit}()` accordingly. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- object-file.c | 9 --------- odb.c | 14 +++++++++++++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/object-file.c b/object-file.c index d7e153c1b9..1b62996ef0 100644 --- a/object-file.c +++ b/object-file.c @@ -1994,15 +1994,8 @@ static void odb_transaction_files_commit(struct odb_transaction *base) { struct odb_transaction_files *transaction = (struct odb_transaction_files *)base; - /* - * Ensure the transaction ending matches the pending transaction. - */ - ASSERT(base == base->source->odb->transaction); - flush_loose_object_transaction(transaction); flush_packfile_transaction(transaction); - base->source->odb->transaction = NULL; - free(transaction); } struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) @@ -2017,8 +2010,6 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) transaction->base.source = source; transaction->base.commit = odb_transaction_files_commit; - odb->transaction = &transaction->base; - return &transaction->base; } diff --git a/odb.c b/odb.c index 349b4218a5..1679cc0465 100644 --- a/odb.c +++ b/odb.c @@ -1153,7 +1153,12 @@ void odb_reprepare(struct object_database *o) struct odb_transaction *odb_transaction_begin(struct object_database *odb) { - return odb_transaction_files_begin(odb->sources); + if (odb->transaction) + return NULL; + + odb->transaction = odb_transaction_files_begin(odb->sources); + + return odb->transaction; } void odb_transaction_commit(struct odb_transaction *transaction) @@ -1161,5 +1166,12 @@ void odb_transaction_commit(struct odb_transaction *transaction) if (!transaction) return; + /* + * Ensure the transaction ending matches the pending transaction. + */ + ASSERT(transaction == transaction->source->odb->transaction); + transaction->commit(transaction); + transaction->source->odb->transaction = NULL; + free(transaction); }