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 <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Feb 2, 2026 at 18:10 UTC fa7d067923a342c298b7723935c60217a5244e4e
4 files changed +65 -43
object-file.c
+44 -36
@@ -710,15 +710,17 @@ struct transaction_packfile {
710 uint32_t nr_written;
711 };
712
713 -struct odb_transaction {
714 - struct odb_source *source;
713 +struct odb_transaction_files {
714 + struct odb_transaction base;
715
716 struct tmp_objdir *objdir;
717 struct transaction_packfile packfile;
718 };
719
720 -static void prepare_loose_object_transaction(struct odb_transaction *transaction)
720 +static void prepare_loose_object_transaction(struct odb_transaction *base)
721 {
722 + struct odb_transaction_files *transaction = (struct odb_transaction_files *)base;
723 +
724 /*
725 * We lazily create the temporary object directory
726 * the first time an object might be added, since
@@ -728,14 +730,16 @@ static void prepare_loose_object_transaction(struct odb_transaction *transaction
730 if (!transaction || transaction->objdir)
731 return;
732
731 - transaction->objdir = tmp_objdir_create(transaction->source->odb->repo, "bulk-fsync");
733 + transaction->objdir = tmp_objdir_create(base->source->odb->repo, "bulk-fsync");
734 if (transaction->objdir)
735 tmp_objdir_replace_primary_odb(transaction->objdir, 0);
736 }
737
736 -static void fsync_loose_object_transaction(struct odb_transaction *transaction,
738 +static void fsync_loose_object_transaction(struct odb_transaction *base,
739 int fd, const char *filename)
740 {
741 + struct odb_transaction_files *transaction = (struct odb_transaction_files *)base;
742 +
743 /*
744 * If we have an active ODB transaction, we issue a call that
745 * cleans the filesystem page cache but avoids a hardware flush
@@ -754,7 +758,7 @@ static void fsync_loose_object_transaction(struct odb_transaction *transaction,
758 /*
759 * Cleanup after batch-mode fsync_object_files.
760 */
757 -static void flush_loose_object_transaction(struct odb_transaction *transaction)
761 +static void flush_loose_object_transaction(struct odb_transaction_files *transaction)
762 {
763 struct strbuf temp_path = STRBUF_INIT;
764 struct tempfile *temp;
@@ -772,7 +776,7 @@ static void flush_loose_object_transaction(struct odb_transaction *transaction)
776 * the final name is visible.
777 */
778 strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX",
775 - repo_get_object_directory(transaction->source->odb->repo));
779 + repo_get_object_directory(transaction->base.source->odb->repo));
780 temp = xmks_tempfile(temp_path.buf);
781 fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
782 delete_tempfile(&temp);
@@ -1340,11 +1344,11 @@ static int index_core(struct index_state *istate,
1344 return ret;
1345 }
1346
1343 -static int already_written(struct odb_transaction *transaction,
1347 +static int already_written(struct odb_transaction_files *transaction,
1348 struct object_id *oid)
1349 {
1350 /* The object may already exist in the repository */
1347 - if (odb_has_object(transaction->source->odb, oid,
1351 + if (odb_has_object(transaction->base.source->odb, oid,
1352 HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
1353 return 1;
1354
@@ -1358,14 +1362,14 @@ static int already_written(struct odb_transaction *transaction,
1362 }
1363
1364 /* Lazily create backing packfile for the state */
1361 -static void prepare_packfile_transaction(struct odb_transaction *transaction,
1365 +static void prepare_packfile_transaction(struct odb_transaction_files *transaction,
1366 unsigned flags)
1367 {
1368 struct transaction_packfile *state = &transaction->packfile;
1369 if (!(flags & INDEX_WRITE_OBJECT) || state->f)
1370 return;
1371
1368 - state->f = create_tmp_packfile(transaction->source->odb->repo,
1372 + state->f = create_tmp_packfile(transaction->base.source->odb->repo,
1373 &state->pack_tmp_name);
1374 reset_pack_idx_option(&state->pack_idx_opts);
1375
@@ -1466,10 +1470,10 @@ static int stream_blob_to_pack(struct transaction_packfile *state,
1470 return 0;
1471 }
1472
1469 -static void flush_packfile_transaction(struct odb_transaction *transaction)
1473 +static void flush_packfile_transaction(struct odb_transaction_files *transaction)
1474 {
1475 struct transaction_packfile *state = &transaction->packfile;
1472 - struct repository *repo = transaction->source->odb->repo;
1476 + struct repository *repo = transaction->base.source->odb->repo;
1477 unsigned char hash[GIT_MAX_RAWSZ];
1478 struct strbuf packname = STRBUF_INIT;
1479 char *idx_tmp_name = NULL;
@@ -1494,7 +1498,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction)
1498 }
1499
1500 strbuf_addf(&packname, "%s/pack/pack-%s.",
1497 - repo_get_object_directory(transaction->source->odb->repo),
1501 + repo_get_object_directory(transaction->base.source->odb->repo),
1502 hash_to_hex_algop(hash, repo->hash_algo));
1503
1504 stage_tmp_packfiles(repo, &packname, state->pack_tmp_name,
@@ -1534,7 +1538,7 @@ clear_exit:
1538 * binary blobs, they generally do not want to get any conversion, and
1539 * callers should avoid this code path when filters are requested.
1540 */
1537 -static int index_blob_packfile_transaction(struct odb_transaction *transaction,
1541 +static int index_blob_packfile_transaction(struct odb_transaction_files *transaction,
1542 struct object_id *result_oid, int fd,
1543 size_t size, const char *path,
1544 unsigned flags)
@@ -1553,7 +1557,7 @@ static int index_blob_packfile_transaction(struct odb_transaction *transaction,
1557
1558 header_len = format_object_header((char *)obuf, sizeof(obuf),
1559 OBJ_BLOB, size);
1556 - transaction->source->odb->repo->hash_algo->init_fn(&ctx);
1560 + transaction->base.source->odb->repo->hash_algo->init_fn(&ctx);
1561 git_hash_update(&ctx, obuf, header_len);
1562
1563 /* Note: idx is non-NULL when we are writing */
@@ -1629,10 +1633,11 @@ int index_fd(struct index_state *istate, struct object_id *oid,
1633 ret = index_core(istate, oid, fd, xsize_t(st->st_size),
1634 type, path, flags);
1635 } else {
1636 + struct object_database *odb = the_repository->objects;
1637 struct odb_transaction *transaction;
1638
1634 - transaction = odb_transaction_begin(the_repository->objects);
1635 - ret = index_blob_packfile_transaction(the_repository->objects->transaction,
1639 + transaction = odb_transaction_begin(odb);
1640 + ret = index_blob_packfile_transaction((struct odb_transaction_files *)odb->transaction,
1641 oid, fd,
1642 xsize_t(st->st_size),
1643 path, flags);
@@ -1985,35 +1990,38 @@ out:
1990 return ret;
1991 }
1992
1988 -struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
1993 +static void odb_transaction_files_commit(struct odb_transaction *base)
1994 {
1990 - struct object_database *odb = source->odb;
1991 -
1992 - if (odb->transaction)
1993 - return NULL;
1994 -
1995 - CALLOC_ARRAY(odb->transaction, 1);
1996 - odb->transaction->source = source;
1997 -
1998 - return odb->transaction;
1999 -}
2000 -
2001 -void odb_transaction_files_commit(struct odb_transaction *transaction)
2002 -{
2003 - if (!transaction)
2004 - return;
1995 + struct odb_transaction_files *transaction = (struct odb_transaction_files *)base;
1996
1997 /*
1998 * Ensure the transaction ending matches the pending transaction.
1999 */
2009 - ASSERT(transaction == transaction->source->odb->transaction);
2000 + ASSERT(base == base->source->odb->transaction);
2001
2002 flush_loose_object_transaction(transaction);
2003 flush_packfile_transaction(transaction);
2013 - transaction->source->odb->transaction = NULL;
2004 + base->source->odb->transaction = NULL;
2005 free(transaction);
2006 }
2007
2008 +struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
2009 +{
2010 + struct odb_transaction_files *transaction;
2011 + struct object_database *odb = source->odb;
2012 +
2013 + if (odb->transaction)
2014 + return NULL;
2015 +
2016 + transaction = xcalloc(1, sizeof(*transaction));
2017 + transaction->base.source = source;
2018 + transaction->base.commit = odb_transaction_files_commit;
2019 +
2020 + odb->transaction = &transaction->base;
2021 +
2022 + return &transaction->base;
2023 +}
2024 +
2025 struct odb_source_loose *odb_source_loose_new(struct odb_source *source)
2026 {
2027 struct odb_source_loose *loose;
object-file.h
-6
@@ -208,10 +208,4 @@ struct odb_transaction;
208 */
209 struct odb_transaction *odb_transaction_files_begin(struct odb_source *source);
210
211 -/*
212 - * Tell the object database to make any objects from the
213 - * current transaction visible.
214 - */
215 -void odb_transaction_files_commit(struct odb_transaction *transaction);
216 -
211 #endif /* OBJECT_FILE_H */
odb.c
+4 -1
@@ -1158,5 +1158,8 @@ struct odb_transaction *odb_transaction_begin(struct object_database *odb)
1158
1159 void odb_transaction_commit(struct odb_transaction *transaction)
1160 {
1161 - odb_transaction_files_commit(transaction);
1161 + if (!transaction)
1162 + return;
1163 +
1164 + transaction->commit(transaction);
1165 }
odb.h
+17
@@ -77,7 +77,24 @@ struct odb_source {
77 struct packed_git;
78 struct packfile_store;
79 struct cached_object_entry;
80 +
81 +/*
82 + * A transaction may be started for an object database prior to writing new
83 + * objects via odb_transaction_begin(). These objects are not committed until
84 + * odb_transaction_commit() is invoked. Only a single transaction may be pending
85 + * at a time.
86 + *
87 + * Each ODB source is expected to implement its own transaction handling.
88 + */
89 struct odb_transaction;
90 +typedef void (*odb_transaction_commit_fn)(struct odb_transaction *transaction);
91 +struct odb_transaction {
92 + /* The ODB source the transaction is opened against. */
93 + struct odb_source *source;
94 +
95 + /* The ODB source specific callback invoked to commit a transaction. */
96 + odb_transaction_commit_fn commit;
97 +};
98
99 /*
100 * The object database encapsulates access to objects in a repository. It