Thread (4 messages) flat view 4 messages, 2 authors, 9d ago
COOLING9d

[PATCH v2] midx-write: skip writes with no object entries

From: Pia Park <hidden>
Date: 2026-09-08 07:10:50
Subsystem: the rest · Maintainer: Linus Torvalds

A MIDX write can find packs without finding any objects, either because
the packs are empty or because an incremental write finds only objects
already indexed by an earlier layer. The existing early exit checks only
the number of packs, so these cases publish a zero-object MIDX without a
reverse index.

Empty MIDXs were already published by 662148c435 (midx: write object
offsets, 2018-07-12); the failure mode appeared in 27afc272c4 (midx:
implement writing incremental MIDX bitmaps, 2025-03-20), when
incremental bitmap writes began loading reverse indexes from prior
layers.

Reproducible on master as of b8242b093d (The 23rd batch, 2026-09-07),
starting with a non-incremental write:

    git init --bare --object-format=sha1 empty.git &&
    (
        cd empty.git &&
        git pack-objects objects/pack/pack </dev/null &&
        git multi-pack-index write &&
        git multi-pack-index write --incremental --bitmap
    )

The first write succeeds but publishes an empty MIDX, which also fails
verification with "the midx contains no oid". The second write exits
with status 255, reporting "could not load reverse index for MIDX".
Starting with an incremental write has the same problem.

Exit before publication whenever the computed entry list is empty, for
both non-incremental and incremental writes, including compaction. Take
the existing cleanup path after pack/drop validation and before
acquiring a lock or creating a temporary MIDX file. This leaves existing
MIDX files untouched and preserves the error when there are no pack
files to index.

Return success silently. Empty-object writes already return 0, including
when --bitmap warns, so preserve that exit status for existing callers
while omitting the warning and empty MIDX.

Test empty packs in a bare repository, empty incremental layers, and
packs containing only objects indexed by an earlier layer. Check silent
success, preservation of existing files, and subsequent writes that add
new objects. Update the existing bitmap test to expect neither an empty
MIDX nor a bitmap.

Signed-off-by: Pia Park <redacted>
---
Thanks, Taylor. I kept exit status 0 because empty-object writes
already succeed, including when --bitmap emits its warning. Would you
prefer an error here instead? The existing no-pack-files error is
unchanged.

Changes since v1:

* Apply the check to all write modes, including compaction, at the
  location in your sketch, after pack/drop validation.
* Preserve exit status 0 and skip empty publication silently.
* Add the non-incremental bare-repository reproduction and check later
  incremental and non-incremental bitmap writes with real objects.
* Update t5326's existing test that expected an empty MIDX and a warning.
  Keep explicit empty-pack and duplicate-only incremental tests.
* Identify the verified 2018 empty-MIDX behavior and the 2025 introduction
  of the reverse-index failure, using reference-format citations.

The implementation passed 885 tests across the eight MIDX/bitmap/repack
scripts. After the test-only revisions, all 481 tests in the three
changed scripts passed, along with test lint and whitespace checks.

 midx-write.c                            |  5 +-
 t/t5319-multi-pack-index.sh             | 35 ++++++++++++
 t/t5326-multi-pack-bitmaps.sh           | 13 +++--
 t/t5334-incremental-multi-pack-index.sh | 75 +++++++++++++++++++++++++
 4 files changed, 119 insertions(+), 9 deletions(-)
diff --git a/midx-write.c b/midx-write.c
index 8537102254..3038bbfad2 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -1617,9 +1617,8 @@ static int write_midx_internal(struct write_midx_opts *opts)
 	}
 
 	if (!ctx.entries_nr) {
-		if (opts->flags & MIDX_WRITE_BITMAP)
-			warning(_("refusing to write multi-pack .bitmap without any objects"));
-		opts->flags &= ~(MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP);
+		result = 0;
+		goto cleanup;
 	}
 
 	if (ctx.incremental) {
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index 68143cb5b7..c239a87d10 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -54,6 +54,41 @@ test_expect_success "don't write midx with no packs" '
 	test_path_is_missing pack/multi-pack-index
 '
 
+test_expect_success 'skip non-incremental MIDX with no objects' '
+	git init --bare empty.git &&
+	(
+		cd empty.git &&
+		git pack-objects objects/pack/pack </dev/null &&
+		ls objects/pack >files.expect &&
+
+		for bitmap in "" --bitmap
+		do
+			git multi-pack-index write $bitmap >out 2>&1 &&
+			test_must_be_empty out &&
+			test_path_is_missing objects/pack/multi-pack-index &&
+			ls objects/pack >files.actual &&
+			test_cmp files.expect files.actual || return 1
+		done &&
+
+		git multi-pack-index write --incremental --bitmap &&
+		test_dir_is_empty objects/pack/multi-pack-index.d &&
+
+		echo blob | git hash-object -w --stdin >in &&
+		git pack-objects objects/pack/pack <in &&
+		git multi-pack-index write --incremental --bitmap &&
+		test_line_count = 1 objects/pack/multi-pack-index.d/multi-pack-index-chain &&
+		git multi-pack-index verify &&
+
+		echo another | git hash-object -w --stdin >in &&
+		git pack-objects objects/pack/pack <in &&
+		git multi-pack-index write --bitmap &&
+		test_path_is_file objects/pack/multi-pack-index &&
+		midx="$(midx_checksum objects)" &&
+		test_path_is_file objects/pack/multi-pack-index-$midx.bitmap &&
+		git multi-pack-index verify
+	)
+'
+
 test_expect_success SHA1 'warn if a midx contains no oid' '
 	cp "$TEST_DIRECTORY"/t5319/no-objects.midx $objdir/pack/multi-pack-index &&
 	test_must_fail git multi-pack-index verify &&
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
index 86beab1dae..490008d1d7 100755
--- a/t/t5326-multi-pack-bitmaps.sh
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -305,7 +305,7 @@ test_midx_bitmap_cases () {
 		)
 	'
 
-	test_expect_success 'no .bitmap is written without any objects' '
+	test_expect_success 'no MIDX or .bitmap is written without any objects' '
 		rm -fr repo &&
 		git init repo &&
 		test_when_finished "rm -fr repo" &&
@@ -318,13 +318,14 @@ test_midx_bitmap_cases () {
 			pack-$empty.idx
 			EOF
 
+			ls $objdir/pack >files.expect &&
 			git multi-pack-index write --bitmap --stdin-packs \
-				<packs 2>err &&
+				<packs >out 2>&1 &&
 
-			test_grep "bitmap without any objects" err &&
-
-			test_path_is_file $midx &&
-			test_path_is_missing $midx-$(midx_checksum $objdir).bitmap
+			test_must_be_empty out &&
+			test_path_is_missing $midx &&
+			ls $objdir/pack >files.actual &&
+			test_cmp files.expect files.actual
 		)
 	'
 
diff --git a/t/t5334-incremental-multi-pack-index.sh b/t/t5334-incremental-multi-pack-index.sh
index f0b82b5f65..fbcc19feeb 100755
--- a/t/t5334-incremental-multi-pack-index.sh
+++ b/t/t5334-incremental-multi-pack-index.sh
@@ -195,4 +195,79 @@ test_expect_success 'non-incremental write with existing incremental chain' '
 	)
 '
 
+test_expect_success 'skip initial MIDX layer with no objects' '
+	git init empty &&
+	(
+		cd empty &&
+		git config maintenance.auto false &&
+		git pack-objects $packdir/pack </dev/null &&
+
+		for bitmap in --bitmap --no-bitmap
+		do
+			git multi-pack-index write --incremental "$bitmap" >out 2>&1 &&
+			test_must_be_empty out &&
+			test_dir_is_empty "$midxdir" || return 1
+		done &&
+
+		write_midx_layer &&
+		test_line_count = 1 "$midx_chain" &&
+		git multi-pack-index verify
+	)
+'
+
+test_expect_success 'skip MIDX layer with empty pack' '
+	git init empty-pack &&
+	(
+		cd empty-pack &&
+		git config maintenance.auto false &&
+		write_midx_layer &&
+
+		git pack-objects $packdir/pack </dev/null &&
+		cp "$midx_chain" chain.expect &&
+		ls "$packdir" "$midxdir" >files.expect &&
+
+		for bitmap in --bitmap --no-bitmap
+		do
+			git multi-pack-index write --incremental "$bitmap" >out 2>&1 &&
+			test_must_be_empty out &&
+			test_cmp chain.expect "$midx_chain" &&
+			ls "$packdir" "$midxdir" >files.actual &&
+			test_cmp files.expect files.actual || return 1
+		done &&
+
+		write_midx_layer &&
+		test_line_count = 2 "$midx_chain" &&
+		git multi-pack-index verify &&
+		git rev-list --test-bitmap 2.2
+	)
+'
+
+test_expect_success 'skip MIDX layer with duplicate pack' '
+	git init duplicate-pack &&
+	(
+		cd duplicate-pack &&
+		git config maintenance.auto false &&
+		write_midx_layer &&
+
+		git rev-parse HEAD^{tree} >in &&
+		git pack-objects $packdir/pack <in &&
+		cp "$midx_chain" chain.expect &&
+		ls "$packdir" "$midxdir" >files.expect &&
+
+		for bitmap in --bitmap --no-bitmap
+		do
+			git multi-pack-index write --incremental "$bitmap" >out 2>&1 &&
+			test_must_be_empty out &&
+			test_cmp chain.expect "$midx_chain" &&
+			ls "$packdir" "$midxdir" >files.actual &&
+			test_cmp files.expect files.actual || return 1
+		done &&
+
+		write_midx_layer &&
+		test_line_count = 2 "$midx_chain" &&
+		git multi-pack-index verify &&
+		git rev-list --test-bitmap 2.2
+	)
+'
+
 test_done
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help