bulk-checkin: remove ODB transaction nesting

ODB transactions support being nested. Only the outermost {begin,end}_odb_transaction() start and finish a transaction. This allows internal object write codepaths to be optimized with ODB transactions without worrying about whether a transaction is already active. When {begin,end}_odb_transaction() is invoked during an active transaction, these operations are essentially treated as no-ops. This can make the interface a bit awkward to use, as calling end_odb_transaction() does not guarantee that a transaction is actually ended. Thus, in situations where a transaction needs to be explicitly flushed, flush_odb_transaction() must be used. To remove the need for an explicit transaction flush operation via flush_odb_transaction() and better clarify transaction semantics, drop the transaction nesting mechanism in favor of begin_odb_transaction() returning a NULL transaction value to signal it was a no-op, and end_odb_transaction() behaving as a no-op when a NULL transaction value is passed. This is safe for existing callers as the transaction value wired to end_odb_transaction() already comes from begin_odb_transaction() and thus continues the same no-op behavior when a transaction is already pending. With this model, passing a pending transaction to end_odb_transaction() ensures it is committed at that point in time. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Sep 16, 2025 at 13:29 UTC f3c1db4b2a23fac171a699b10f9328f8df52602f
3 files changed +14 -18
bulk-checkin.c
+10 -12
@@ -33,7 +33,6 @@ struct bulk_checkin_packfile {
33 struct odb_transaction {
34 struct object_database *odb;
35
36 - int nesting;
36 struct tmp_objdir *objdir;
37 struct bulk_checkin_packfile packfile;
38 };
@@ -368,12 +367,11 @@ void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction,
367
368 struct odb_transaction *begin_odb_transaction(struct object_database *odb)
369 {
371 - if (!odb->transaction) {
372 - CALLOC_ARRAY(odb->transaction, 1);
373 - odb->transaction->odb = odb;
374 - }
370 + if (odb->transaction)
371 + return NULL;
372
376 - odb->transaction->nesting += 1;
373 + CALLOC_ARRAY(odb->transaction, 1);
374 + odb->transaction->odb = odb;
375
376 return odb->transaction;
377 }
@@ -389,14 +387,14 @@ void flush_odb_transaction(struct odb_transaction *transaction)
387
388 void end_odb_transaction(struct odb_transaction *transaction)
389 {
392 - if (!transaction || transaction->nesting == 0)
393 - BUG("Unbalanced ODB transaction nesting");
394 -
395 - transaction->nesting -= 1;
396 -
397 - if (transaction->nesting)
390 + if (!transaction)
391 return;
392
393 + /*
394 + * Ensure the transaction ending matches the pending transaction.
395 + */
396 + ASSERT(transaction == transaction->odb->transaction);
397 +
398 flush_odb_transaction(transaction);
399 transaction->odb->transaction = NULL;
400 free(transaction);
bulk-checkin.h
+3 -5
@@ -38,9 +38,8 @@ int index_blob_bulk_checkin(struct odb_transaction *transaction,
38 /*
39 * Tell the object database to optimize for adding
40 * multiple objects. end_odb_transaction must be called
41 - * to make new objects visible. Transactions can be nested,
42 - * and objects are only visible after the outermost transaction
43 - * is complete or the transaction is flushed.
41 + * to make new objects visible. If a transaction is already
42 + * pending, NULL is returned.
43 */
44 struct odb_transaction *begin_odb_transaction(struct object_database *odb);
45
@@ -53,8 +52,7 @@ void flush_odb_transaction(struct odb_transaction *transaction);
52
53 /*
54 * Tell the object database to make any objects from the
56 - * current transaction visible if this is the final nested
57 - * transaction.
55 + * current transaction visible.
56 */
57 void end_odb_transaction(struct odb_transaction *transaction);
58
object-file.c
+1 -1
@@ -1267,7 +1267,7 @@ int index_fd(struct index_state *istate, struct object_id *oid,
1267 struct odb_transaction *transaction;
1268
1269 transaction = begin_odb_transaction(the_repository->objects);
1270 - ret = index_blob_bulk_checkin(transaction,
1270 + ret = index_blob_bulk_checkin(the_repository->objects->transaction,
1271 oid, fd, xsize_t(st->st_size),
1272 path, flags);
1273 end_odb_transaction(transaction);