repack: support combining '--geometric' with '--cruft'
Teach 'git repack' to accept '--geometric' and '--cruft' together. When
both are given, the geometric repack rolls up non-cruft packs as usual,
and a separate cruft pack is written to collect unreachable objects.
Previously, '--cruft' implied `ALL_INTO_ONE`, which is fundamentally
incompatible with geometric repacking. Relax this so that '--cruft' only
implies `ALL_INTO_ONE` when '--geometric' is not also given.
When combining the two modes:
- Use the new '--stdin-packs=follow-reachable' mode so that only
reachable objects from the rolled-up packs (and any reachable loose
objects) appear in the geometric pack. Unreachable objects are left
for the cruft writer to collect.
- Plumb our `pack_geometry` into `write_cruft_pack()`, so that the
latter can tell 'pack-objects' which non-kept packs are below the
split (excluded, so their unreachable objects are candidates for the
cruft pack) versus above the split (included, so they are treated as
reachable).
- Handle promisor packs in the cruft writer's geometry path, since
promisor packs have their own split point.
- Use the refs snapshot (when available) so that pack-objects and the
MIDX bitmap writer see the same set of reference tips.
Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Taylor Blau committedJun 26, 2026 at 15:02 UTCa53c3b6193d783f1dd2bd283782b1c3033ef0b3e
5 files changed+300-11
Documentation/git-repack.adoc
+11
index 72c42015e2..e9df771327 100644--- a/Documentation/git-repack.adoc+++ b/Documentation/git-repack.adoc@@ -70,6 +70,11 @@ to the new separate pack will be written. are packed into a separate cruft pack. Unreachable objects can be pruned using the normal expiry rules with the next `git gc` invocation (see linkgit:git-gc[1]). Incompatible with `-k`.+++When combined with `--geometric`, `--cruft` does not imply `-a`. Instead,+the geometric repack rolls up packs as usual, and a separate cruft pack is+written to collect unreachable objects. Only reachable objects from the+rolled-up packs are included in the resulting geometric pack. --cruft-expiration=<approxidate>:: Expire unreachable objects older than `<approxidate>`@@ -245,6 +250,12 @@ progression. Loose objects are implicitly included in this "roll-up", without respect to their reachability. This is subject to change in the future. ++When combined with `--cruft`, only reachable objects from rolled-up packs+are included in the geometric pack, along with any reachable loose objects.+Unreachable objects (both from rolled-up packs and loose) are collected+into a separate cruft pack. Existing cruft packs are retained. See+`--cruft` above for details.++ When writing a multi-pack bitmap, `git repack` selects the largest resulting pack as the preferred pack for object selection by the MIDX (see linkgit:git-multi-pack-index[1]).
builtin/repack.c
+16-7
index dfb6fed231..165cfff75c 100644--- a/builtin/repack.c+++ b/builtin/repack.c@@ -260,7 +260,7 @@ int cmd_repack(int argc, keep_unreachable, "-k/--keep-unreachable", pack_everything & PACK_CRUFT, "--cruft");- if (pack_everything & PACK_CRUFT)+ if (pack_everything & PACK_CRUFT && !geometry.split_factor) pack_everything |= ALL_INTO_ONE; if (write_bitmaps < 0) {@@ -296,7 +296,8 @@ int cmd_repack(int argc, die(_("invalid value for %s: %d"), "--midx-new-layer-threshold", config_ctx.midx_new_layer_threshold);- if (write_midx != REPACK_WRITE_MIDX_NONE && write_bitmaps) {+ if ((write_midx != REPACK_WRITE_MIDX_NONE && write_bitmaps) ||+ (geometry.split_factor && (pack_everything & PACK_CRUFT))) { struct strbuf path = STRBUF_INIT; strbuf_addf(&path, "%s/%s_XXXXXX",@@ -317,7 +318,7 @@ int cmd_repack(int argc, existing_packs_collect(&existing, &keep_pack_list); if (geometry.split_factor) {- if (pack_everything)+ if (pack_everything & ~PACK_CRUFT) die(_("options '%s' and '%s' cannot be used together"), "--geometric", "-A/-a"); if (write_midx == REPACK_WRITE_MIDX_INCREMENTAL) { geometry.midx_layer_threshold = config_ctx.midx_new_layer_threshold;@@ -393,10 +394,16 @@ int cmd_repack(int argc, pack_geometry_repack_promisors(repo, &po_args, &geometry, &names, packtmp);- if (midx_must_contain_cruft)+ if (pack_everything & PACK_CRUFT) {+ strvec_push(&cmd.args, "--stdin-packs=follow-reachable");+ if (refs_snapshot)+ strvec_pushf(&cmd.args, "--refs-snapshot=%s",+ get_tempfile_path(refs_snapshot));+ } else if (midx_must_contain_cruft) strvec_push(&cmd.args, "--stdin-packs"); else strvec_push(&cmd.args, "--stdin-packs=follow");+ strvec_push(&cmd.args, "--unpacked"); } else { strvec_push(&cmd.args, "--unpacked");@@ -431,7 +438,8 @@ int cmd_repack(int argc, const char *basename = pack_basename(geometry.pack[i]); char marker = '^';- if (!midx_must_contain_cruft &&+ if ((pack_everything & PACK_CRUFT ||+ !midx_must_contain_cruft) && !string_list_has_string(&existing.midx_packs, basename)) { /*@@ -505,7 +513,8 @@ int cmd_repack(int argc, ret = write_cruft_pack(&opts, cruft_expiration, combine_cruft_below_size, &names,- &existing);+ &existing,+ geometry.split_factor ? &geometry : NULL); if (ret) goto cleanup;@@ -540,7 +549,7 @@ int cmd_repack(int argc, */ opts.destination = expire_to; ret = write_cruft_pack(&opts, NULL, 0ul, &names,- &existing);+ &existing, NULL); if (ret) goto cleanup; }