In 37dc6d8104 (builtin/repack.c: implement support for
`--max-cruft-size`, 2023-10-02), we exposed new functionality that
allowed repositories to specify the behavior of when we should combine
multiple cruft packs together.
This feature was designed to ensure that we never repacked cruft packs
which were larger than the given threshold in order to provide tighter
I/O bounds for repositories that have many unreachable objects. In
essence, specifying '--max-cruft-size=N' instructed 'repack' to
aggregate cruft packs together (in order of ascending size) until the
combine size grows past 'N', and then make a new cruft pack whose
contents includes the packs we rolled up.
But this isn't quite how it works in practice. Suppose for example that
we have two cruft packs which are each 100MiB in size. One might expect
specifying "--max-cruft-size=200M" would combine these two packs
together, and then avoid repacking them until a pruning GC takes place.
In reality, 'repack' would try and aggregate these together, but writing
a pack that is strictly smaller than 200 MiB (since pack-objects'
"--max-pack-size" provides a strict bound for packs containing more than
one object).
So instead we'll write out a pack that is, say, 199 MiB in size, and
then another 1 MiB pack containing the balance. If we later repack the
repository without adding any new unreachable objects, we'll repeat the
same exercise again, making the same 199 MiB and 1 MiB packs each time.
This happens because of a poor choice to bolt the '--max-cruft-size'
functionality onto pack-objects' '--max-pack-size', forcing us to
generate packs which are always smaller than the provided threshold and
thus subject to repacking.
The following commit will introduce a new flag that implements something
similar to the behavior above. Let's prepare for that by making repack's
'--max-cruft-size' flag behave as an cruft pack-specific override for
'--max-pack-size'.
Do so by temporarily repurposing the 'collapse_small_cruft_packs()'
function to instead generate a cruft pack using the same instructions as
if we didn't specify any maximum pack size. The calling code looks
something like:
if (args->max_pack_size && !cruft_expiration) {
collapse_small_cruft_packs(in, args->max_pack_size, existing);
} else {
for_each_string_list_item(item, &existing->non_kept_packs)
fprintf(in, "-%s.pack\n", item->string);
for_each_string_list_item(item, &existing->cruft_packs)
fprintf(in, "-%s.pack\n", item->string);
}
This patch makes collapse_small_cruft_packs() behave identically to the
'else' arm of the conditional above. This repurposing of
'collapse_small_cruft_packs()' is intentional, since it will set us up
nicely to introduce the new behavior in the following commit.
Naturally, there is some test fallout in the test which exercises the
old meaning of '--max-cruft-size'. Mark that test as failing for now to
be dealt with in the following commit. Likewise, add a new test which
explicitly tests the behavior of '--max-cruft-size' to place a hard
limit on the size of any generated cruft pack(s).
Note that this is a breaking change, as it alters the user-visible
behavior of '--max-cruft-size'. But I'm OK changing this behavior in
this instance, since the behavior wasn't accurate to begin with.
Signed-off-by: Taylor Blau <me@ttaylorr.com>
Acked-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
724 lines
20 KiB
Bash
Executable File
724 lines
20 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
test_description='git repack works correctly'
|
|
|
|
. ./test-lib.sh
|
|
|
|
objdir=.git/objects
|
|
packdir=$objdir/pack
|
|
|
|
test_expect_success '--expire-to stores pruned objects (now)' '
|
|
git init expire-to-now &&
|
|
(
|
|
cd expire-to-now &&
|
|
|
|
git branch -M main &&
|
|
|
|
test_commit base &&
|
|
|
|
git checkout -b cruft &&
|
|
test_commit --no-tag cruft &&
|
|
|
|
git rev-list --objects --no-object-names main..cruft >moved.raw &&
|
|
sort moved.raw >moved.want &&
|
|
|
|
git rev-list --all --objects --no-object-names >expect.raw &&
|
|
sort expect.raw >expect &&
|
|
|
|
git checkout main &&
|
|
git branch -D cruft &&
|
|
git reflog expire --all --expire=all &&
|
|
|
|
git init --bare expired.git &&
|
|
git repack -d \
|
|
--cruft --cruft-expiration="now" \
|
|
--expire-to="expired.git/objects/pack/pack" &&
|
|
|
|
expired="$(ls expired.git/objects/pack/pack-*.idx)" &&
|
|
test_path_is_file "${expired%.idx}.mtimes" &&
|
|
|
|
# Since the `--cruft-expiration` is "now", the effective
|
|
# behavior is to move _all_ unreachable objects out to
|
|
# the location in `--expire-to`.
|
|
git show-index <$expired >expired.raw &&
|
|
cut -d" " -f2 expired.raw | sort >expired.objects &&
|
|
git rev-list --all --objects --no-object-names \
|
|
>remaining.objects &&
|
|
|
|
# ...in other words, the combined contents of this
|
|
# repository and expired.git should be the same as the
|
|
# set of objects we started with.
|
|
sort expired.objects remaining.objects >actual &&
|
|
test_cmp expect actual &&
|
|
|
|
# The "moved" objects (i.e., those in expired.git)
|
|
# should be the same as the cruft objects which were
|
|
# expired in the previous step.
|
|
test_cmp moved.want expired.objects
|
|
)
|
|
'
|
|
|
|
test_expect_success '--expire-to stores pruned objects (5.minutes.ago)' '
|
|
git init expire-to-5.minutes.ago &&
|
|
(
|
|
cd expire-to-5.minutes.ago &&
|
|
|
|
git branch -M main &&
|
|
|
|
test_commit base &&
|
|
|
|
# Create two classes of unreachable objects, one which
|
|
# is older than 5 minutes (stale), and another which is
|
|
# newer (recent).
|
|
for kind in stale recent
|
|
do
|
|
git checkout -b $kind main &&
|
|
test_commit --no-tag $kind || return 1
|
|
done &&
|
|
|
|
git rev-list --objects --no-object-names main..stale >in &&
|
|
stale="$(git pack-objects $objdir/pack/pack <in)" &&
|
|
mtime="$(test-tool chmtime --get =-600 $objdir/pack/pack-$stale.pack)" &&
|
|
|
|
# expect holds the set of objects we expect to find in
|
|
# this repository after repacking
|
|
git rev-list --objects --no-object-names recent >expect.raw &&
|
|
sort expect.raw >expect &&
|
|
|
|
# moved.want holds the set of objects we expect to find
|
|
# in expired.git
|
|
git rev-list --objects --no-object-names main..stale >out &&
|
|
sort out >moved.want &&
|
|
|
|
git checkout main &&
|
|
git branch -D stale recent &&
|
|
git reflog expire --all --expire=all &&
|
|
git prune-packed &&
|
|
|
|
git init --bare expired.git &&
|
|
git repack -d \
|
|
--cruft --cruft-expiration=5.minutes.ago \
|
|
--expire-to="expired.git/objects/pack/pack" &&
|
|
|
|
# Some of the remaining objects in this repository are
|
|
# unreachable, so use `cat-file --batch-all-objects`
|
|
# instead of `rev-list` to get their names
|
|
git cat-file --batch-all-objects --batch-check="%(objectname)" \
|
|
>remaining.objects &&
|
|
sort remaining.objects >actual &&
|
|
test_cmp expect actual &&
|
|
|
|
(
|
|
cd expired.git &&
|
|
|
|
expired="$(ls objects/pack/pack-*.mtimes)" &&
|
|
test-tool pack-mtimes $(basename $expired) >out &&
|
|
cut -d" " -f1 out | sort >../moved.got &&
|
|
|
|
# Ensure that there are as many objects with the
|
|
# expected mtime as were moved to expired.git.
|
|
#
|
|
# In other words, ensure that the recorded
|
|
# mtimes of any moved objects was written
|
|
# correctly.
|
|
grep " $mtime$" out >matching &&
|
|
test_line_count = $(wc -l <../moved.want) matching
|
|
) &&
|
|
test_cmp moved.want moved.got
|
|
)
|
|
'
|
|
|
|
generate_random_blob() {
|
|
test-tool genrandom "$@" >blob &&
|
|
git hash-object -w -t blob blob &&
|
|
rm blob
|
|
}
|
|
|
|
pack_random_blob () {
|
|
generate_random_blob "$@" &&
|
|
git repack -d -q >/dev/null
|
|
}
|
|
|
|
generate_cruft_pack () {
|
|
pack_random_blob "$@" >/dev/null &&
|
|
|
|
ls $packdir/pack-*.pack | xargs -n 1 basename >in &&
|
|
pack="$(git pack-objects --cruft $packdir/pack <in)" &&
|
|
git prune-packed &&
|
|
|
|
echo "$packdir/pack-$pack.mtimes"
|
|
}
|
|
|
|
test_expect_success '--max-cruft-size creates new packs when too large' '
|
|
git init max-cruft-size-large &&
|
|
(
|
|
cd max-cruft-size-large &&
|
|
test_commit base &&
|
|
|
|
foo="$(pack_random_blob foo $((1*1024*1024)))" &&
|
|
git repack --cruft -d &&
|
|
cruft_foo="$(ls $packdir/pack-*.mtimes)" &&
|
|
|
|
bar="$(pack_random_blob bar $((1*1024*1024)))" &&
|
|
git repack --cruft -d --max-cruft-size=1M &&
|
|
cruft_bar="$(ls $packdir/pack-*.mtimes | grep -v $cruft_foo)" &&
|
|
|
|
test-tool pack-mtimes $(basename "$cruft_foo") >foo.objects &&
|
|
test-tool pack-mtimes $(basename "$cruft_bar") >bar.objects &&
|
|
|
|
grep "^$foo" foo.objects &&
|
|
test_line_count = 1 foo.objects &&
|
|
grep "^$bar" bar.objects &&
|
|
test_line_count = 1 bar.objects
|
|
)
|
|
'
|
|
|
|
test_expect_success '--max-cruft-size combines existing packs when not too large' '
|
|
git init max-cruft-size-small &&
|
|
(
|
|
cd max-cruft-size-small &&
|
|
test_commit base &&
|
|
|
|
foo="$(pack_random_blob foo $((1*1024*1024)))" &&
|
|
git repack --cruft -d &&
|
|
|
|
bar="$(pack_random_blob bar $((1*1024*1024)))" &&
|
|
git repack --cruft -d --max-cruft-size=10M &&
|
|
|
|
cruft=$(ls $packdir/pack-*.mtimes) &&
|
|
test-tool pack-mtimes $(basename "$cruft") >cruft.objects &&
|
|
|
|
grep "^$foo" cruft.objects &&
|
|
grep "^$bar" cruft.objects &&
|
|
test_line_count = 2 cruft.objects
|
|
)
|
|
'
|
|
|
|
test_expect_failure '--max-cruft-size combines smaller packs first' '
|
|
git init max-cruft-size-consume-small &&
|
|
(
|
|
cd max-cruft-size-consume-small &&
|
|
|
|
test_commit base &&
|
|
git repack -ad &&
|
|
|
|
cruft_foo="$(generate_cruft_pack foo 524288)" && # 0.5 MiB
|
|
cruft_bar="$(generate_cruft_pack bar 524288)" && # 0.5 MiB
|
|
cruft_baz="$(generate_cruft_pack baz 1048576)" && # 1.0 MiB
|
|
cruft_quux="$(generate_cruft_pack quux 1572864)" && # 1.5 MiB
|
|
|
|
test-tool pack-mtimes "$(basename $cruft_foo)" >expect.raw &&
|
|
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
|
|
ls $packdir/pack-*.mtimes | sort >cruft.before &&
|
|
git repack -d --cruft --max-cruft-size=2M &&
|
|
ls $packdir/pack-*.mtimes | sort >cruft.after &&
|
|
|
|
comm -13 cruft.before cruft.after >cruft.new &&
|
|
comm -23 cruft.before cruft.after >cruft.removed &&
|
|
|
|
test_line_count = 1 cruft.new &&
|
|
test_line_count = 2 cruft.removed &&
|
|
|
|
# the two smaller packs should be rolled up first
|
|
printf "%s\n" $cruft_foo $cruft_bar | sort >expect.removed &&
|
|
test_cmp expect.removed cruft.removed &&
|
|
|
|
# ...and contain the set of objects rolled up
|
|
test-tool pack-mtimes "$(basename $(cat cruft.new))" >actual.raw &&
|
|
sort actual.raw >actual.objects &&
|
|
|
|
test_cmp expect.objects actual.objects
|
|
)
|
|
'
|
|
|
|
test_expect_success 'setup cruft with freshened objects' '
|
|
git init cruft-freshen &&
|
|
(
|
|
cd cruft-freshen &&
|
|
|
|
test_commit base &&
|
|
git repack -ad &&
|
|
|
|
foo="$(generate_random_blob foo 64)" &&
|
|
test-tool chmtime --get -10000 \
|
|
"$objdir/$(test_oid_to_path "$foo")" >foo.mtime &&
|
|
|
|
git repack --cruft -d &&
|
|
|
|
cruft="$(ls $packdir/pack-*.mtimes)" &&
|
|
test-tool pack-mtimes "$(basename $cruft)" >actual &&
|
|
echo "$foo $(cat foo.mtime)" >expect &&
|
|
test_cmp expect actual
|
|
)
|
|
'
|
|
|
|
test_expect_success 'cruft with freshened objects (loose)' '
|
|
(
|
|
cd cruft-freshen &&
|
|
|
|
# regenerate the object, setting its mtime to be more recent
|
|
foo="$(generate_random_blob foo 64)" &&
|
|
test-tool chmtime --get -100 \
|
|
"$objdir/$(test_oid_to_path "$foo")" >foo.mtime &&
|
|
|
|
git repack --cruft -d &&
|
|
|
|
cruft="$(ls $packdir/pack-*.mtimes)" &&
|
|
test-tool pack-mtimes "$(basename $cruft)" >actual &&
|
|
echo "$foo $(cat foo.mtime)" >expect &&
|
|
test_cmp expect actual
|
|
)
|
|
'
|
|
|
|
test_expect_success 'cruft with freshened objects (packed)' '
|
|
(
|
|
cd cruft-freshen &&
|
|
|
|
# regenerate the object and store it in a packfile,
|
|
# setting its mtime to be more recent
|
|
#
|
|
# store it alongside another cruft object so that we
|
|
# do not create an identical copy of the existing
|
|
# cruft pack (which contains $foo).
|
|
foo="$(generate_random_blob foo 64)" &&
|
|
bar="$(generate_random_blob bar 64)" &&
|
|
foo_pack="$(printf "%s\n" $foo $bar | git pack-objects $packdir/pack)" &&
|
|
git prune-packed &&
|
|
|
|
test-tool chmtime --get -10 \
|
|
"$packdir/pack-$foo_pack.pack" >foo.mtime &&
|
|
|
|
git repack --cruft -d &&
|
|
|
|
cruft="$(ls $packdir/pack-*.mtimes)" &&
|
|
test-tool pack-mtimes "$(basename $cruft)" >actual &&
|
|
echo "$foo $(cat foo.mtime)" >expect.raw &&
|
|
echo "$bar $(cat foo.mtime)" >>expect.raw &&
|
|
sort expect.raw >expect &&
|
|
test_cmp expect actual
|
|
)
|
|
'
|
|
|
|
test_expect_success 'multi-cruft with freshened objects (previously cruft)' '
|
|
repo="max-cruft-size-threshold" &&
|
|
|
|
test_when_finished "rm -fr $repo" &&
|
|
git init "$repo" &&
|
|
(
|
|
cd "$repo" &&
|
|
|
|
test_commit base &&
|
|
foo="$(generate_random_blob foo $((2*1024*1024)))" &&
|
|
bar="$(generate_random_blob bar $((2*1024*1024)))" &&
|
|
baz="$(generate_random_blob baz $((2*1024*1024)))" &&
|
|
|
|
test-tool chmtime --get -100000 \
|
|
"$objdir/$(test_oid_to_path "$foo")" >foo.old &&
|
|
test-tool chmtime --get -100000 \
|
|
"$objdir/$(test_oid_to_path "$bar")" >bar.old &&
|
|
test-tool chmtime --get -100000 \
|
|
"$objdir/$(test_oid_to_path "$baz")" >baz.old &&
|
|
|
|
git repack --cruft -d &&
|
|
|
|
# Make an identical copy of foo stored in a pack with a more
|
|
# recent mtime.
|
|
foo="$(generate_random_blob foo $((2*1024*1024)))" &&
|
|
foo_pack="$(echo "$foo" | git pack-objects $packdir/pack)" &&
|
|
test-tool chmtime --get -100 \
|
|
"$packdir/pack-$foo_pack.pack" >foo.new &&
|
|
git prune-packed &&
|
|
|
|
# Make a loose copy of bar, also with a more recent mtime.
|
|
bar="$(generate_random_blob bar $((2*1024*1024)))" &&
|
|
test-tool chmtime --get -100 \
|
|
"$objdir/$(test_oid_to_path "$bar")" >bar.new &&
|
|
|
|
# Make a new cruft object $quux to ensure we do not
|
|
# generate an identical pack to the existing cruft
|
|
# pack.
|
|
quux="$(generate_random_blob quux $((1024)))" &&
|
|
test-tool chmtime --get -100 \
|
|
"$objdir/$(test_oid_to_path "$quux")" >quux.new &&
|
|
|
|
git repack --cruft --max-cruft-size=3M -d &&
|
|
|
|
for p in $packdir/pack-*.mtimes
|
|
do
|
|
test-tool pack-mtimes "$(basename "$p")" || return 1
|
|
done >actual.raw &&
|
|
sort actual.raw >actual &&
|
|
|
|
# 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.new)
|
|
$bar $(cat bar.new)
|
|
$baz $(cat baz.old)
|
|
$quux $(cat quux.new)
|
|
EOF
|
|
|
|
test_cmp expect actual
|
|
)
|
|
'
|
|
|
|
test_expect_success '--max-cruft-size with pruning' '
|
|
git init max-cruft-size-prune &&
|
|
(
|
|
cd max-cruft-size-prune &&
|
|
|
|
test_commit base &&
|
|
foo="$(generate_random_blob foo $((1024*1024)))" &&
|
|
bar="$(generate_random_blob bar $((1024*1024)))" &&
|
|
baz="$(generate_random_blob baz $((1024*1024)))" &&
|
|
|
|
test-tool chmtime -10000 "$objdir/$(test_oid_to_path "$foo")" &&
|
|
|
|
git repack -d --cruft --max-cruft-size=1M &&
|
|
|
|
# backdate the mtimes of all cruft packs to validate
|
|
# that they were rewritten as a result of pruning
|
|
ls $packdir/pack-*.mtimes | sort >cruft.before &&
|
|
for cruft in $(cat cruft.before)
|
|
do
|
|
mtime="$(test-tool chmtime --get -10000 "$cruft")" &&
|
|
echo $cruft $mtime >>mtimes || return 1
|
|
done &&
|
|
|
|
# repack (and prune) with a --max-cruft-size to ensure
|
|
# that we appropriately split the resulting set of packs
|
|
git repack -d --cruft --max-cruft-size=1M \
|
|
--cruft-expiration=1000.seconds.ago &&
|
|
ls $packdir/pack-*.mtimes | sort >cruft.after &&
|
|
|
|
for cruft in $(cat cruft.after)
|
|
do
|
|
old_mtime="$(grep $cruft mtimes | cut -d" " -f2)" &&
|
|
new_mtime="$(test-tool chmtime --get $cruft)" &&
|
|
test $old_mtime -lt $new_mtime || return 1
|
|
done &&
|
|
|
|
test_line_count = 3 cruft.before &&
|
|
test_line_count = 2 cruft.after &&
|
|
test_must_fail git cat-file -e $foo &&
|
|
git cat-file -e $bar &&
|
|
git cat-file -e $baz
|
|
)
|
|
'
|
|
|
|
test_expect_success '--max-cruft-size ignores non-local packs' '
|
|
repo="max-cruft-size-non-local" &&
|
|
git init $repo &&
|
|
(
|
|
cd $repo &&
|
|
test_commit base &&
|
|
generate_random_blob foo 64 &&
|
|
git repack --cruft -d
|
|
) &&
|
|
|
|
git clone --reference=$repo $repo $repo-alt &&
|
|
(
|
|
cd $repo-alt &&
|
|
|
|
test_commit other &&
|
|
generate_random_blob bar 64 &&
|
|
|
|
# ensure that we do not attempt to pick up packs from
|
|
# the non-alternated repository, which would result in a
|
|
# crash
|
|
git repack --cruft --max-cruft-size=1M -d
|
|
)
|
|
'
|
|
|
|
test_expect_success 'reachable packs are preferred over cruft ones' '
|
|
repo="cruft-preferred-packs" &&
|
|
git init "$repo" &&
|
|
(
|
|
cd "$repo" &&
|
|
|
|
# This test needs to exercise careful control over when a MIDX
|
|
# is and is not written. Unset the corresponding TEST variable
|
|
# accordingly.
|
|
sane_unset GIT_TEST_MULTI_PACK_INDEX &&
|
|
|
|
test_commit base &&
|
|
test_commit --no-tag cruft &&
|
|
|
|
non_cruft="$(echo base | git pack-objects --revs $packdir/pack)" &&
|
|
# Write a cruft pack which both (a) sorts ahead of the non-cruft
|
|
# pack in lexical order, and (b) has an older mtime to appease
|
|
# the MIDX preferred pack selection routine.
|
|
cruft="$(echo pack-$non_cruft.pack | git pack-objects --cruft $packdir/pack-A)" &&
|
|
test-tool chmtime -1000 $packdir/pack-A-$cruft.pack &&
|
|
|
|
test_commit other &&
|
|
git repack -d &&
|
|
|
|
git repack --geometric 2 -d --write-midx --write-bitmap-index &&
|
|
|
|
# After repacking, there are two packs left: one reachable one
|
|
# (which is the result of combining both of the existing two
|
|
# non-cruft packs), and one cruft pack.
|
|
find .git/objects/pack -type f -name "*.pack" >packs &&
|
|
test_line_count = 2 packs &&
|
|
|
|
# Make sure that the pack we just wrote is marked as preferred,
|
|
# not the cruft one.
|
|
pack="$(test-tool read-midx --preferred-pack $objdir)" &&
|
|
test_path_is_missing "$packdir/$(basename "$pack" ".idx").mtimes"
|
|
)
|
|
'
|
|
|
|
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
|