aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-repack.adoc21
-rw-r--r--builtin/repack.c62
-rwxr-xr-xt/t5329-pack-objects-cruft.sh302
-rwxr-xr-xt/t7704-repack-cruft.sh293
4 files changed, 355 insertions, 323 deletions
diff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc
index 5852a5c973..e1cd75eebe 100644
--- a/Documentation/git-repack.adoc
+++ b/Documentation/git-repack.adoc
@@ -77,15 +77,18 @@ to the new separate pack will be written.
Only useful with `--cruft -d`.
--max-cruft-size=<n>::
- Repack cruft objects into packs as large as `<n>` bytes before
- creating new packs. As long as there are enough cruft packs
- smaller than `<n>`, repacking will cause a new cruft pack to
- be created containing objects from any combined cruft packs,
- along with any new unreachable objects. Cruft packs larger than
- `<n>` will not be modified. When the new cruft pack is larger
- than `<n>` bytes, it will be split into multiple packs, all of
- which are guaranteed to be at most `<n>` bytes in size. Only
- useful with `--cruft -d`.
+ Overrides `--max-pack-size` for cruft packs. Inherits the value of
+ `--max-pack-size` (if any) by default. See the documentation for
+ `--max-pack-size` for more details.
+
+--combine-cruft-below-size=<n>::
+ When generating cruft packs without pruning, only repack
+ existing cruft packs whose size is strictly less than `<n>`,
+ where `<n>` represents a number of bytes, which can optionally
+ be suffixed with "k", "m", or "g". Cruft packs whose size is
+ greater than or equal to `<n>` are left as-is and not repacked.
+ Useful when you want to avoid repacking large cruft pack(s) in
+ repositories that have many and/or large unreachable objects.
--expire-to=<dir>::
Write a cruft pack containing pruned objects (if any) to the
diff --git a/builtin/repack.c b/builtin/repack.c
index 75e3752353..f3330ade7b 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -1022,29 +1022,13 @@ static int write_filtered_pack(const struct pack_objects_args *args,
return finish_pack_objects_cmd(&cmd, names, local);
}
-static int existing_cruft_pack_cmp(const void *va, const void *vb)
+static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,
+ struct existing_packs *existing)
{
- struct packed_git *a = *(struct packed_git **)va;
- struct packed_git *b = *(struct packed_git **)vb;
-
- if (a->pack_size < b->pack_size)
- return -1;
- if (a->pack_size > b->pack_size)
- return 1;
- return 0;
-}
-
-static void collapse_small_cruft_packs(FILE *in, size_t max_size,
- struct existing_packs *existing)
-{
- struct packed_git **existing_cruft, *p;
+ struct packed_git *p;
struct strbuf buf = STRBUF_INIT;
- size_t total_size = 0;
- size_t existing_cruft_nr = 0;
size_t i;
- ALLOC_ARRAY(existing_cruft, existing->cruft_packs.nr);
-
for (p = get_all_packs(the_repository); p; p = p->next) {
if (!(p->is_cruft && p->pack_local))
continue;
@@ -1056,24 +1040,7 @@ static void collapse_small_cruft_packs(FILE *in, size_t max_size,
if (!string_list_has_string(&existing->cruft_packs, buf.buf))
continue;
- if (existing_cruft_nr >= existing->cruft_packs.nr)
- BUG("too many cruft packs (found %"PRIuMAX", but knew "
- "of %"PRIuMAX")",
- (uintmax_t)existing_cruft_nr + 1,
- (uintmax_t)existing->cruft_packs.nr);
- existing_cruft[existing_cruft_nr++] = p;
- }
-
- QSORT(existing_cruft, existing_cruft_nr, existing_cruft_pack_cmp);
-
- for (i = 0; i < existing_cruft_nr; i++) {
- size_t proposed;
-
- p = existing_cruft[i];
- proposed = st_add(total_size, p->pack_size);
-
- if (proposed <= max_size) {
- total_size = proposed;
+ if (p->pack_size < combine_cruft_below_size) {
fprintf(in, "-%s\n", pack_basename(p));
} else {
retain_cruft_pack(existing, p);
@@ -1086,13 +1053,13 @@ static void collapse_small_cruft_packs(FILE *in, size_t max_size,
existing->non_kept_packs.items[i].string);
strbuf_release(&buf);
- free(existing_cruft);
}
static int write_cruft_pack(const struct pack_objects_args *args,
const char *destination,
const char *pack_prefix,
const char *cruft_expiration,
+ unsigned long combine_cruft_below_size,
struct string_list *names,
struct existing_packs *existing)
{
@@ -1135,8 +1102,9 @@ static int write_cruft_pack(const struct pack_objects_args *args,
in = xfdopen(cmd.in, "w");
for_each_string_list_item(item, names)
fprintf(in, "%s-%s.pack\n", pack_prefix, item->string);
- if (args->max_pack_size && !cruft_expiration) {
- collapse_small_cruft_packs(in, args->max_pack_size, existing);
+ if (combine_cruft_below_size && !cruft_expiration) {
+ combine_small_cruft_packs(in, combine_cruft_below_size,
+ existing);
} else {
for_each_string_list_item(item, &existing->non_kept_packs)
fprintf(in, "-%s.pack\n", item->string);
@@ -1190,6 +1158,7 @@ int cmd_repack(int argc,
const char *opt_window_memory = NULL;
const char *opt_depth = NULL;
const char *opt_threads = NULL;
+ unsigned long combine_cruft_below_size = 0ul;
struct option builtin_repack_options[] = {
OPT_BIT('a', NULL, &pack_everything,
@@ -1202,6 +1171,9 @@ int cmd_repack(int argc,
PACK_CRUFT),
OPT_STRING(0, "cruft-expiration", &cruft_expiration, N_("approxidate"),
N_("with --cruft, expire objects older than this")),
+ OPT_MAGNITUDE(0, "combine-cruft-below-size",
+ &combine_cruft_below_size,
+ N_("with --cruft, only repack cruft packs smaller than this")),
OPT_MAGNITUDE(0, "max-cruft-size", &cruft_po_args.max_pack_size,
N_("with --cruft, limit the size of new cruft packs")),
OPT_BOOL('d', NULL, &delete_redundant,
@@ -1445,7 +1417,8 @@ int cmd_repack(int argc,
cruft_po_args.quiet = po_args.quiet;
ret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,
- cruft_expiration, &names,
+ cruft_expiration,
+ combine_cruft_below_size, &names,
&existing);
if (ret)
goto cleanup;
@@ -1472,10 +1445,17 @@ int cmd_repack(int argc,
* generate an empty pack (since every object not in the
* cruft pack generated above will have an mtime older
* than the expiration).
+ *
+ * Pretend we don't have a `--combine-cruft-below-size`
+ * argument, since we're not selectively combining
+ * anything based on size to generate the limbo cruft
+ * pack, but rather removing all cruft packs from the
+ * main repository regardless of size.
*/
ret = write_cruft_pack(&cruft_po_args, expire_to,
pack_prefix,
NULL,
+ 0ul,
&names,
&existing);
if (ret)
diff --git a/t/t5329-pack-objects-cruft.sh b/t/t5329-pack-objects-cruft.sh
index b71a0aef40..25ddda5cf3 100755
--- a/t/t5329-pack-objects-cruft.sh
+++ b/t/t5329-pack-objects-cruft.sh
@@ -360,43 +360,6 @@ test_expect_success 'expired objects are pruned' '
)
'
-test_expect_success 'repack --cruft generates a cruft pack' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit reachable &&
- git branch -M main &&
- git checkout --orphan other &&
- test_commit unreachable &&
-
- git checkout main &&
- git branch -D other &&
- git tag -d unreachable &&
- # objects are not cruft if they are contained in the reflogs
- git reflog expire --all --expire=all &&
-
- git rev-list --objects --all --no-object-names >reachable.raw &&
- git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
- sort <reachable.raw >reachable &&
- comm -13 reachable objects >unreachable &&
-
- git repack --cruft -d &&
-
- cruft=$(basename $(ls $packdir/pack-*.mtimes) .mtimes) &&
- pack=$(basename $(ls $packdir/pack-*.pack | grep -v $cruft) .pack) &&
-
- git show-index <$packdir/$pack.idx >actual.raw &&
- cut -f2 -d" " actual.raw | sort >actual &&
- test_cmp reachable actual &&
-
- git show-index <$packdir/$cruft.idx >actual.raw &&
- cut -f2 -d" " actual.raw | sort >actual &&
- test_cmp unreachable actual
- )
-'
-
test_expect_success 'loose objects mtimes upsert others' '
git init repo &&
test_when_finished "rm -fr repo" &&
@@ -470,219 +433,6 @@ test_expect_success 'expiring cruft objects with git gc' '
)
'
-test_expect_success 'cruft packs are not included in geometric repack' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit reachable &&
- git repack -Ad &&
- git branch -M main &&
-
- git checkout --orphan other &&
- test_commit cruft &&
- git repack -d &&
-
- git checkout main &&
- git branch -D other &&
- git tag -d cruft &&
- git reflog expire --all --expire=all &&
-
- git repack --cruft &&
-
- find $packdir -type f | sort >before &&
- git repack --geometric=2 -d &&
- find $packdir -type f | sort >after &&
-
- test_cmp before after
- )
-'
-
-test_expect_success 'repack --geometric collects once-cruft objects' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit reachable &&
- git repack -Ad &&
- git branch -M main &&
-
- git checkout --orphan other &&
- git rm -rf . &&
- test_commit --no-tag cruft &&
- cruft="$(git rev-parse HEAD)" &&
-
- git checkout main &&
- git branch -D other &&
- git reflog expire --all --expire=all &&
-
- # Pack the objects created in the previous step into a cruft
- # pack. Intentionally leave loose copies of those objects
- # around so we can pick them up in a subsequent --geometric
- # reapack.
- git repack --cruft &&
-
- # Now make those objects reachable, and ensure that they are
- # packed into the new pack created via a --geometric repack.
- git update-ref refs/heads/other $cruft &&
-
- # Without this object, the set of unpacked objects is exactly
- # the set of objects already in the cruft pack. Tweak that set
- # to ensure we do not overwrite the cruft pack entirely.
- test_commit reachable2 &&
-
- find $packdir -name "pack-*.idx" | sort >before &&
- git repack --geometric=2 -d &&
- find $packdir -name "pack-*.idx" | sort >after &&
-
- {
- git rev-list --objects --no-object-names $cruft &&
- git rev-list --objects --no-object-names reachable..reachable2
- } >want.raw &&
- sort want.raw >want &&
-
- pack=$(comm -13 before after) &&
- git show-index <$pack >objects.raw &&
-
- cut -d" " -f2 objects.raw | sort >got &&
-
- test_cmp want got
- )
-'
-
-test_expect_success 'cruft repack with no reachable objects' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit base &&
- git repack -ad &&
-
- base="$(git rev-parse base)" &&
-
- git for-each-ref --format="delete %(refname)" >in &&
- git update-ref --stdin <in &&
- git reflog expire --all --expire=all &&
- rm -fr .git/index &&
-
- git repack --cruft -d &&
-
- git cat-file -t $base
- )
-'
-
-write_blob () {
- test-tool genrandom "$@" >in &&
- git hash-object -w -t blob in
-}
-
-find_pack () {
- for idx in $(ls $packdir/pack-*.idx)
- do
- git show-index <$idx >out &&
- if grep -q "$1" out
- then
- echo $idx
- fi || return 1
- done
-}
-
-test_expect_success 'cruft repack with --max-pack-size' '
- git init max-pack-size &&
- (
- cd max-pack-size &&
- test_commit base &&
-
- # two cruft objects which exceed the maximum pack size
- foo=$(write_blob foo 1048576) &&
- bar=$(write_blob bar 1048576) &&
- test-tool chmtime --get -1000 \
- "$objdir/$(test_oid_to_path $foo)" >foo.mtime &&
- test-tool chmtime --get -2000 \
- "$objdir/$(test_oid_to_path $bar)" >bar.mtime &&
- git repack --cruft --max-pack-size=1M &&
- find $packdir -name "*.mtimes" >cruft &&
- test_line_count = 2 cruft &&
-
- foo_mtimes="$(basename $(find_pack $foo) .idx).mtimes" &&
- bar_mtimes="$(basename $(find_pack $bar) .idx).mtimes" &&
- test-tool pack-mtimes $foo_mtimes >foo.actual &&
- test-tool pack-mtimes $bar_mtimes >bar.actual &&
-
- echo "$foo $(cat foo.mtime)" >foo.expect &&
- echo "$bar $(cat bar.mtime)" >bar.expect &&
-
- test_cmp foo.expect foo.actual &&
- test_cmp bar.expect bar.actual &&
- test "$foo_mtimes" != "$bar_mtimes"
- )
-'
-
-test_expect_success 'cruft repack with pack.packSizeLimit' '
- (
- cd max-pack-size &&
- # repack everything back together to remove the existing cruft
- # pack (but to keep its objects)
- git repack -adk &&
- git -c pack.packSizeLimit=1M repack --cruft &&
- # ensure the same post condition is met when --max-pack-size
- # would otherwise be inferred from the configuration
- find $packdir -name "*.mtimes" >cruft &&
- test_line_count = 2 cruft &&
- for pack in $(cat cruft)
- do
- test-tool pack-mtimes "$(basename $pack)" >objects &&
- test_line_count = 1 objects || return 1
- done
- )
-'
-
-test_expect_success 'cruft repack respects repack.cruftWindow' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit base &&
-
- GIT_TRACE2_EVENT=$(pwd)/event.trace \
- git -c pack.window=1 -c repack.cruftWindow=2 repack \
- --cruft --window=3 &&
-
- grep "pack-objects.*--window=2.*--cruft" event.trace
- )
-'
-
-test_expect_success 'cruft repack respects --window by default' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit base &&
-
- GIT_TRACE2_EVENT=$(pwd)/event.trace \
- git -c pack.window=2 repack --cruft --window=3 &&
-
- grep "pack-objects.*--window=3.*--cruft" event.trace
- )
-'
-
-test_expect_success 'cruft repack respects --quiet' '
- git init repo &&
- test_when_finished "rm -fr repo" &&
- (
- cd repo &&
-
- test_commit base &&
- GIT_PROGRESS_DELAY=0 git repack --cruft --quiet 2>err &&
- test_must_be_empty err
- )
-'
-
test_expect_success 'cruft --local drops unreachable objects' '
git init alternate &&
git init repo &&
@@ -945,4 +695,56 @@ test_expect_success 'additional cruft blobs via gc.recentObjectsHook' '
)
'
+test_expect_success 'split cruft packs with --max-cruft-size' '
+ repo=cruft-with--max-cruft-size &&
+ test_when_finished "rm -fr $repo" &&
+
+ git init "$repo" &&
+
+ (
+ cd "$repo" &&
+
+ git config core.compression 0 &&
+
+ sz=$((1024 * 1024)) && # 1MiB
+ test-tool genrandom foo $sz >foo &&
+ test-tool genrandom bar $sz >bar &&
+ foo="$(git hash-object -w -t blob foo)" &&
+ bar="$(git hash-object -w -t blob bar)" &&
+
+ to=$packdir/pack &&
+ # Pack together foo and bar into a single 2MiB pack.
+ pack="$(git pack-objects $to <<-EOF
+ $foo
+ $bar
+ EOF
+ )" &&
+
+ # Then generate a cruft pack containing foo and bar.
+ #
+ # Generate the pack with --max-pack-size equal to the
+ # size of one object, forcing us to write two cruft
+ # packs.
+ git pack-objects --cruft --max-pack-size=$sz $to <<-EOF &&
+ -pack-$pack.pack
+ EOF
+
+ ls $packdir/pack-*.mtimes >crufts &&
+ test_line_count = 2 crufts &&
+
+ for cruft in $(cat crufts)
+ do
+ test-tool pack-mtimes "$(basename "$cruft")" || return 1
+ done >actual.raw &&
+
+ cut -d" " -f1 <actual.raw | sort >actual &&
+ sort >expect <<-EOF &&
+ $foo
+ $bar
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh
index 43d2947d28..8aebfb45f5 100755
--- a/t/t7704-repack-cruft.sh
+++ b/t/t7704-repack-cruft.sh
@@ -149,7 +149,7 @@ generate_cruft_pack () {
echo "$packdir/pack-$pack.mtimes"
}
-test_expect_success '--max-cruft-size creates new packs when above threshold' '
+test_expect_success '--max-cruft-size creates new packs when too large' '
git init max-cruft-size-large &&
(
cd max-cruft-size-large &&
@@ -173,7 +173,7 @@ test_expect_success '--max-cruft-size creates new packs when above threshold' '
)
'
-test_expect_success '--max-cruft-size combines existing packs when below threshold' '
+test_expect_success '--max-cruft-size combines existing packs when not too large' '
git init max-cruft-size-small &&
(
cd max-cruft-size-small &&
@@ -194,10 +194,13 @@ test_expect_success '--max-cruft-size combines existing packs when below thresho
)
'
-test_expect_success '--max-cruft-size combines smaller packs first' '
- git init max-cruft-size-consume-small &&
+test_expect_success '--combine-cruft-below-size combines packs' '
+ repo=combine-cruft-below-size &&
+ test_when_finished "rm -fr $repo" &&
+
+ git init "$repo" &&
(
- cd max-cruft-size-consume-small &&
+ cd "$repo" &&
test_commit base &&
git repack -ad &&
@@ -211,11 +214,11 @@ test_expect_success '--max-cruft-size combines smaller packs first' '
test-tool pack-mtimes "$(basename $cruft_bar)" >>expect.raw &&
sort expect.raw >expect.objects &&
- # repacking with `--max-cruft-size=2M` should combine
- # both 0.5 MiB packs together, instead of, say, one of
- # the 0.5 MiB packs with the 1.0 MiB pack
+ # Repacking with `--combine-cruft-below-size=1M`
+ # should combine both 0.5 MiB packs together, but
+ # ignore the two packs which are >= 1.0 MiB.
ls $packdir/pack-*.mtimes | sort >cruft.before &&
- git repack -d --cruft --max-cruft-size=2M &&
+ git repack -d --cruft --combine-cruft-below-size=1M &&
ls $packdir/pack-*.mtimes | sort >cruft.after &&
comm -13 cruft.before cruft.after >cruft.new &&
@@ -224,11 +227,12 @@ test_expect_success '--max-cruft-size combines smaller packs first' '
test_line_count = 1 cruft.new &&
test_line_count = 2 cruft.removed &&
- # the two smaller packs should be rolled up first
+ # The two packs smaller than 1.0MiB should be repacked
+ # together.
printf "%s\n" $cruft_foo $cruft_bar | sort >expect.removed &&
test_cmp expect.removed cruft.removed &&
- # ...and contain the set of objects rolled up
+ # ...and contain the set of objects rolled up.
test-tool pack-mtimes "$(basename $(cat cruft.new))" >actual.raw &&
sort actual.raw >actual.objects &&
@@ -236,10 +240,10 @@ test_expect_success '--max-cruft-size combines smaller packs first' '
)
'
-test_expect_success 'setup --max-cruft-size with freshened objects' '
- git init max-cruft-size-freshen &&
+test_expect_success 'setup cruft with freshened objects' '
+ git init cruft-freshen &&
(
- cd max-cruft-size-freshen &&
+ cd cruft-freshen &&
test_commit base &&
git repack -ad &&
@@ -257,9 +261,9 @@ test_expect_success 'setup --max-cruft-size with freshened objects' '
)
'
-test_expect_success '--max-cruft-size with freshened objects (loose)' '
+test_expect_success 'cruft with freshened objects (loose)' '
(
- cd max-cruft-size-freshen &&
+ cd cruft-freshen &&
# regenerate the object, setting its mtime to be more recent
foo="$(generate_random_blob foo 64)" &&
@@ -275,9 +279,9 @@ test_expect_success '--max-cruft-size with freshened objects (loose)' '
)
'
-test_expect_success '--max-cruft-size with freshened objects (packed)' '
+test_expect_success 'cruft with freshened objects (packed)' '
(
- cd max-cruft-size-freshen &&
+ cd cruft-freshen &&
# regenerate the object and store it in a packfile,
# setting its mtime to be more recent
@@ -304,7 +308,7 @@ test_expect_success '--max-cruft-size with freshened objects (packed)' '
)
'
-test_expect_success '--max-cruft-size with freshened objects (previously cruft)' '
+test_expect_success 'multi-cruft with freshened objects (previously cruft)' '
repo="max-cruft-size-threshold" &&
test_when_finished "rm -fr $repo" &&
@@ -354,13 +358,11 @@ test_expect_success '--max-cruft-size with freshened objects (previously cruft)'
done >actual.raw &&
sort actual.raw >actual &&
- # Among the set of all cruft packs, we should see both
- # mtimes for object $foo and $bar, as well as the
+ # Among the set of all cruft packs, we should see the
+ # new mtimes for object $foo and $bar, as well as the
# single new copy of $baz.
sort >expect <<-EOF &&
- $foo $(cat foo.old)
$foo $(cat foo.new)
- $bar $(cat bar.old)
$bar $(cat bar.new)
$baz $(cat baz.old)
$quux $(cat quux.new)
@@ -477,4 +479,249 @@ test_expect_success 'reachable packs are preferred over cruft ones' '
)
'
+test_expect_success 'repack --cruft generates a cruft pack' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit reachable &&
+ git branch -M main &&
+ git checkout --orphan other &&
+ test_commit unreachable &&
+
+ git checkout main &&
+ git branch -D other &&
+ git tag -d unreachable &&
+ # objects are not cruft if they are contained in the reflogs
+ git reflog expire --all --expire=all &&
+
+ git rev-list --objects --all --no-object-names >reachable.raw &&
+ git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
+ sort <reachable.raw >reachable &&
+ comm -13 reachable objects >unreachable &&
+
+ git repack --cruft -d &&
+
+ cruft=$(basename $(ls $packdir/pack-*.mtimes) .mtimes) &&
+ pack=$(basename $(ls $packdir/pack-*.pack | grep -v $cruft) .pack) &&
+
+ git show-index <$packdir/$pack.idx >actual.raw &&
+ cut -f2 -d" " actual.raw | sort >actual &&
+ test_cmp reachable actual &&
+
+ git show-index <$packdir/$cruft.idx >actual.raw &&
+ cut -f2 -d" " actual.raw | sort >actual &&
+ test_cmp unreachable actual
+ )
+'
+
+test_expect_success 'cruft packs are not included in geometric repack' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit reachable &&
+ git repack -Ad &&
+ git branch -M main &&
+
+ git checkout --orphan other &&
+ test_commit cruft &&
+ git repack -d &&
+
+ git checkout main &&
+ git branch -D other &&
+ git tag -d cruft &&
+ git reflog expire --all --expire=all &&
+
+ git repack --cruft &&
+
+ find $packdir -type f | sort >before &&
+ git repack --geometric=2 -d &&
+ find $packdir -type f | sort >after &&
+
+ test_cmp before after
+ )
+'
+
+test_expect_success 'repack --geometric collects once-cruft objects' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit reachable &&
+ git repack -Ad &&
+ git branch -M main &&
+
+ git checkout --orphan other &&
+ git rm -rf . &&
+ test_commit --no-tag cruft &&
+ cruft="$(git rev-parse HEAD)" &&
+
+ git checkout main &&
+ git branch -D other &&
+ git reflog expire --all --expire=all &&
+
+ # Pack the objects created in the previous step into a cruft
+ # pack. Intentionally leave loose copies of those objects
+ # around so we can pick them up in a subsequent --geometric
+ # reapack.
+ git repack --cruft &&
+
+ # Now make those objects reachable, and ensure that they are
+ # packed into the new pack created via a --geometric repack.
+ git update-ref refs/heads/other $cruft &&
+
+ # Without this object, the set of unpacked objects is exactly
+ # the set of objects already in the cruft pack. Tweak that set
+ # to ensure we do not overwrite the cruft pack entirely.
+ test_commit reachable2 &&
+
+ find $packdir -name "pack-*.idx" | sort >before &&
+ git repack --geometric=2 -d &&
+ find $packdir -name "pack-*.idx" | sort >after &&
+
+ {
+ git rev-list --objects --no-object-names $cruft &&
+ git rev-list --objects --no-object-names reachable..reachable2
+ } >want.raw &&
+ sort want.raw >want &&
+
+ pack=$(comm -13 before after) &&
+ git show-index <$pack >objects.raw &&
+
+ cut -d" " -f2 objects.raw | sort >got &&
+
+ test_cmp want got
+ )
+'
+
+test_expect_success 'cruft repack with no reachable objects' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+ git repack -ad &&
+
+ base="$(git rev-parse base)" &&
+
+ git for-each-ref --format="delete %(refname)" >in &&
+ git update-ref --stdin <in &&
+ git reflog expire --all --expire=all &&
+ rm -fr .git/index &&
+
+ git repack --cruft -d &&
+
+ git cat-file -t $base
+ )
+'
+
+find_pack () {
+ for idx in $(ls $packdir/pack-*.idx)
+ do
+ git show-index <$idx >out &&
+ if grep -q "$1" out
+ then
+ echo $idx
+ fi || return 1
+ done
+}
+
+test_expect_success 'cruft repack with --max-pack-size' '
+ git init max-pack-size &&
+ (
+ cd max-pack-size &&
+ test_commit base &&
+
+ # two cruft objects which exceed the maximum pack size
+ foo=$(generate_random_blob foo 1048576) &&
+ bar=$(generate_random_blob bar 1048576) &&
+ test-tool chmtime --get -1000 \
+ "$objdir/$(test_oid_to_path $foo)" >foo.mtime &&
+ test-tool chmtime --get -2000 \
+ "$objdir/$(test_oid_to_path $bar)" >bar.mtime &&
+ git repack --cruft --max-pack-size=1M &&
+ find $packdir -name "*.mtimes" >cruft &&
+ test_line_count = 2 cruft &&
+
+ foo_mtimes="$(basename $(find_pack $foo) .idx).mtimes" &&
+ bar_mtimes="$(basename $(find_pack $bar) .idx).mtimes" &&
+ test-tool pack-mtimes $foo_mtimes >foo.actual &&
+ test-tool pack-mtimes $bar_mtimes >bar.actual &&
+
+ echo "$foo $(cat foo.mtime)" >foo.expect &&
+ echo "$bar $(cat bar.mtime)" >bar.expect &&
+
+ test_cmp foo.expect foo.actual &&
+ test_cmp bar.expect bar.actual &&
+ test "$foo_mtimes" != "$bar_mtimes"
+ )
+'
+
+test_expect_success 'cruft repack with pack.packSizeLimit' '
+ (
+ cd max-pack-size &&
+ # repack everything back together to remove the existing cruft
+ # pack (but to keep its objects)
+ git repack -adk &&
+ git -c pack.packSizeLimit=1M repack --cruft &&
+ # ensure the same post condition is met when --max-pack-size
+ # would otherwise be inferred from the configuration
+ find $packdir -name "*.mtimes" >cruft &&
+ test_line_count = 2 cruft &&
+ for pack in $(cat cruft)
+ do
+ test-tool pack-mtimes "$(basename $pack)" >objects &&
+ test_line_count = 1 objects || return 1
+ done
+ )
+'
+
+test_expect_success 'cruft repack respects repack.cruftWindow' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+
+ GIT_TRACE2_EVENT=$(pwd)/event.trace \
+ git -c pack.window=1 -c repack.cruftWindow=2 repack \
+ --cruft --window=3 &&
+
+ grep "pack-objects.*--window=2.*--cruft" event.trace
+ )
+'
+
+test_expect_success 'cruft repack respects --window by default' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+
+ GIT_TRACE2_EVENT=$(pwd)/event.trace \
+ git -c pack.window=2 repack --cruft --window=3 &&
+
+ grep "pack-objects.*--window=3.*--cruft" event.trace
+ )
+'
+
+test_expect_success 'cruft repack respects --quiet' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+ GIT_PROGRESS_DELAY=0 git repack --cruft --quiet 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done