bulk-checkin: require transaction for index_blob_bulk_checkin()

The bulk-checkin subsystem provides a mechanism to write blobs directly to a packfile via `index_blob_bulk_checkin()`. If there is an ongoing transaction when invoked, objects written via this function are stored in the same packfile. The packfile is not flushed until the transaction itself is flushed. If there is no transaction, the single object is written to a packfile and immediately flushed. This complicates `index_blob_bulk_checkin()` as it cannot reliably use the provided transaction to get the associated repository. Update `index_blob_bulk_checkin()` to assume that a valid transaction is always provided. Callers are now expected to ensure a transaction is set up beforehand. With this simplification, `deflate_blob_bulk_checkin()` is no longer needed as a standalone internal function and is combined with `index_blob_bulk_checkin()`. The single call site in `object-file.c:index_fd()` is updated accordingly. Due to how `{begin,end}_odb_transaction()` handles nested transactions, a new transaction is only created and committed if there is not already an ongoing transaction. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Aug 22, 2025 at 16:34 UTC aa4d81b53311fcdf099400beebad99c14be4b561
3 files changed +23 -32
bulk-checkin.c
+4 -23
@@ -254,11 +254,11 @@ static void prepare_to_stream(struct bulk_checkin_packfile *state,
254 die_errno("unable to write pack header");
255 }
256
257 -static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
258 - struct object_id *result_oid,
259 - int fd, size_t size,
260 - const char *path, unsigned flags)
257 +int index_blob_bulk_checkin(struct odb_transaction *transaction,
258 + struct object_id *result_oid, int fd, size_t size,
259 + const char *path, unsigned flags)
260 {
261 + struct bulk_checkin_packfile *state = &transaction->packfile;
262 off_t seekback, already_hashed_to;
263 struct git_hash_ctx ctx;
264 unsigned char obuf[16384];
@@ -361,25 +361,6 @@ void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction,
361 }
362 }
363
364 -int index_blob_bulk_checkin(struct odb_transaction *transaction,
365 - struct object_id *oid, int fd, size_t size,
366 - const char *path, unsigned flags)
367 -{
368 - int status;
369 -
370 - if (transaction) {
371 - status = deflate_blob_to_pack(&transaction->packfile, oid, fd,
372 - size, path, flags);
373 - } else {
374 - struct bulk_checkin_packfile state = { 0 };
375 -
376 - status = deflate_blob_to_pack(&state, oid, fd, size, path, flags);
377 - flush_bulk_checkin_packfile(&state);
378 - }
379 -
380 - return status;
381 -}
382 -
364 struct odb_transaction *begin_odb_transaction(struct object_database *odb)
365 {
366 if (!odb->transaction) {
bulk-checkin.h
+5 -2
@@ -14,8 +14,11 @@ void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction,
14 int fd, const char *filename);
15
16 /*
17 - * This creates one packfile per large blob unless bulk-checkin
18 - * machinery is "plugged".
17 + * This writes the specified object to a packfile. Objects written here
18 + * during the same transaction are written to the same packfile. The
19 + * packfile is not flushed until the transaction is flushed. The caller
20 + * is expected to ensure a valid transaction is setup for objects to be
21 + * recorded to.
22 *
23 * This also bypasses the usual "convert-to-git" dance, and that is on
24 * purpose. We could write a streaming version of the converting
object-file.c
+14 -7
@@ -1253,19 +1253,26 @@ int index_fd(struct index_state *istate, struct object_id *oid,
1253 * Call xsize_t() only when needed to avoid potentially unnecessary
1254 * die() for large files.
1255 */
1256 - if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path))
1256 + if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path)) {
1257 ret = index_stream_convert_blob(istate, oid, fd, path, flags);
1258 - else if (!S_ISREG(st->st_mode))
1258 + } else if (!S_ISREG(st->st_mode)) {
1259 ret = index_pipe(istate, oid, fd, type, path, flags);
1260 - else if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||
1261 - type != OBJ_BLOB ||
1262 - (path && would_convert_to_git(istate, path)))
1260 + } else if ((st->st_size >= 0 &&
1261 + (size_t)st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||
1262 + type != OBJ_BLOB ||
1263 + (path && would_convert_to_git(istate, path))) {
1264 ret = index_core(istate, oid, fd, xsize_t(st->st_size),
1265 type, path, flags);
1265 - else
1266 - ret = index_blob_bulk_checkin(the_repository->objects->transaction,
1266 + } else {
1267 + struct odb_transaction *transaction;
1268 +
1269 + transaction = begin_odb_transaction(the_repository->objects);
1270 + ret = index_blob_bulk_checkin(transaction,
1271 oid, fd, xsize_t(st->st_size),
1272 path, flags);
1273 + end_odb_transaction(transaction);
1274 + }
1275 +
1276 close(fd);
1277 return ret;
1278 }