repack: allow `--write-midx=incremental` without `--geometric`
Previously, `--write-midx=incremental` required `--geometric` and would
die() without it. Relax this restriction so that incremental MIDX
repacking can be used independently.
Without `--geometric`, the behavior is append-only: a single new MIDX
layer is created containing whatever packs were written by the repack
and appended to the existing chain (or a new chain is started). Existing
layers are preserved as-is with no compaction or merging.
Implement this via a new repack_make_midx_append_plan() that builds a
plan consisting of a WRITE step for the freshly written packs followed
by COPY steps for every existing MIDX layer. The existing compaction
plan (repack_make_midx_compaction_plan) is used only when `--geometric`
is active.
Update the documentation to describe the behavior with and without
`--geometric`, and replace the test that enforced the old restriction
with one exercising append-only incremental MIDX repacking.
Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Taylor Blau committedMay 19, 2026 at 11:58 UTC06733a50eeec4205011d210d3932c5b708a665e9
4 files changed+133-18
Documentation/git-repack.adoc
+12-7
index 27a99cc46f..72c42015e2 100644--- a/Documentation/git-repack.adoc+++ b/Documentation/git-repack.adoc@@ -263,14 +263,19 @@ linkgit:git-multi-pack-index[1]). `incremental`;; Write an incremental MIDX chain instead of a single- flat MIDX. This mode requires `--geometric`.+ flat MIDX. +-The incremental mode maintains a chain of MIDX layers that is compacted-over time using a geometric merging strategy. Each repack creates a new-tip layer containing the newly written pack(s). Adjacent layers are then-merged whenever the newer layer's object count exceeds-`1/repack.midxSplitFactor` of the next deeper layer's count. Layers-that do not meet this condition are retained as-is.+Without `--geometric`, a new MIDX layer is appended to the existing+chain (or a new chain is started) containing whatever packs were written+by the repack. Existing layers are preserved as-is.+++When combined with `--geometric`, the incremental mode maintains a chain+of MIDX layers that is compacted over time using a geometric merging+strategy. Each repack creates a new tip layer containing the newly+written pack(s). Adjacent layers are then merged whenever the newer+layer's object count exceeds `1/repack.midxSplitFactor` of the next+deeper layer's count. Layers that do not meet this condition are+retained as-is. + The result is that newer (tip) layers tend to contain many small packs with relatively few objects, while older (deeper) layers contain fewer,
builtin/repack.c
-3
index 5ffa18e085..1524a9c13a 100644--- a/builtin/repack.c+++ b/builtin/repack.c@@ -263,9 +263,6 @@ int cmd_repack(int argc, if (pack_everything & PACK_CRUFT) pack_everything |= ALL_INTO_ONE;- if (write_midx == REPACK_WRITE_MIDX_INCREMENTAL && !geometry.split_factor)- die(_("--write-midx=incremental requires --geometric"));- if (write_bitmaps < 0) { if (write_midx == REPACK_WRITE_MIDX_NONE && (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository()))
repack-midx.c
+61-3
index 4f5deeb97b..b6b1de7180 100644--- a/repack-midx.c+++ b/repack-midx.c@@ -548,6 +548,60 @@ static void midx_compaction_step_release(struct midx_compaction_step *step) free(step->csum); }+/*+ * Build an append-only MIDX plan: a single WRITE step for the freshly+ * written packs, plus COPY steps for every existing layer. No+ * compaction or merging is performed.+ */+static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts,+ struct midx_compaction_step **steps_p,+ size_t *steps_nr_p)+{+ struct multi_pack_index *m;+ struct midx_compaction_step *steps = NULL;+ struct midx_compaction_step *step;+ size_t steps_nr = 0, steps_alloc = 0;++ odb_reprepare(opts->existing->repo->objects);+ m = get_multi_pack_index(opts->existing->source);++ if (opts->names->nr) {+ struct strbuf buf = STRBUF_INIT;+ uint32_t i;++ ALLOC_GROW(steps, st_add(steps_nr, 1), steps_alloc);++ step = &steps[steps_nr++];+ memset(step, 0, sizeof(*step));++ step->type = MIDX_COMPACTION_STEP_WRITE;+ string_list_init_dup(&step->u.write);++ for (i = 0; i < opts->names->nr; i++) {+ strbuf_reset(&buf);+ strbuf_addf(&buf, "pack-%s.idx",+ opts->names->items[i].string);+ string_list_append(&step->u.write, buf.buf);+ }++ strbuf_release(&buf);+ }++ for (; m; m = m->base_midx) {+ ALLOC_GROW(steps, st_add(steps_nr, 1), steps_alloc);++ step = &steps[steps_nr++];+ memset(step, 0, sizeof(*step));++ step->type = MIDX_COMPACTION_STEP_COPY;+ step->u.copy = m;+ step->objects_nr = m->num_objects;+ }++ *steps_p = steps;+ *steps_nr_p = steps_nr;+}+ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts, struct midx_compaction_step **steps_p, size_t *steps_nr_p)@@ -904,9 +958,13 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts) goto done; }- if (repack_make_midx_compaction_plan(opts, &steps, &steps_nr) < 0) {- ret = error(_("unable to generate compaction plan"));- goto done;+ if (opts->geometry->split_factor) {+ if (repack_make_midx_compaction_plan(opts, &steps, &steps_nr) < 0) {+ ret = error(_("unable to generate compaction plan"));+ goto done;+ }+ } else {+ repack_make_midx_append_plan(opts, &steps, &steps_nr); } for (i = 0; i < steps_nr; i++) {
t/t7705-repack-incremental-midx.sh
+60-5
index 9e317ff6e8..25a8c40e8e 100755--- a/t/t7705-repack-incremental-midx.sh+++ b/t/t7705-repack-incremental-midx.sh@@ -63,10 +63,36 @@ create_layers () { done }-test_expect_success '--write-midx=incremental requires --geometric' '- test_must_fail git repack --write-midx=incremental 2>err &&+test_expect_success '--write-midx=incremental without --geometric' '+ git init incremental-without-geometric &&+ (+ cd incremental-without-geometric &&++ git config maintenance.auto false &&++ test_commit first &&+ git repack -d &&- test_grep -- "--write-midx=incremental requires --geometric" err+ test_commit second &&+ git repack --write-midx=incremental &&++ git multi-pack-index verify &&+ test_line_count = 1 $midx_chain &&+ cp $midx_chain $midx_chain.before &&++ # A second repack appends a new layer without+ # disturbing the existing one.+ test_commit third &&+ git repack --write-midx=incremental &&++ git multi-pack-index verify &&+ test_line_count = 2 $midx_chain &&+ head -n 1 $midx_chain.before >expect &&+ head -n 1 $midx_chain >actual &&+ test_cmp expect actual &&++ git fsck+ ) ' test_expect_success 'below layer threshold, tip packs excluded' '@@ -334,8 +360,7 @@ test_expect_success 'kept packs are excluded from repack' ' # entirely, so no rollup occurs as there is only one # non-kept pack. A new MIDX layer is written containing # that pack.- git repack --geometric=2 -d --write-midx=incremental \- --write-bitmap-index &&+ git repack --geometric=2 -d --write-midx=incremental && test-tool read-midx $objdir >actual && grep "^pack-.*\.idx$" actual >actual.packs &&@@ -433,6 +458,36 @@ test_expect_success 'repack -ad removes stale incremental chain' ' ) '+test_expect_success 'repack -ad --write-midx=incremental is safe' '+ git init ad-incremental-midx &&+ (+ cd ad-incremental-midx &&++ git config maintenance.auto false &&++ # Build a MIDX chain with multiple layers referencing+ # distinct packs.+ test_commit first &&+ git repack -d &&++ test_commit second &&+ git repack -d --write-midx=incremental &&++ git multi-pack-index verify &&+ test_line_count = 1 $midx_chain &&++ # Now do a full -ad repack. The new pack contains all+ # objects, but any retained MIDX layers still reference+ # the now-deleted packs.+ test_commit third &&+ git repack -ad --write-midx=incremental &&++ git multi-pack-index verify &&+ git fsck &&+ git rev-list --all --objects >/dev/null+ )+'+ test_expect_success 'repack rejects invalid midxSplitFactor' ' test_when_finished "rm -fr bad-split-factor" && git init bad-split-factor &&