| #!/bin/sh |
| |
| test_description='git repack works correctly' |
| |
| TEST_PASSES_SANITIZE_LEAK=true |
| . ./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 above threshold' ' |
| 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 below threshold' ' |
| 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_success '--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 --max-cruft-size with freshened objects' ' |
| git init max-cruft-size-freshen && |
| ( |
| cd max-cruft-size-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 '--max-cruft-size with freshened objects (loose)' ' |
| ( |
| cd max-cruft-size-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 '--max-cruft-size with freshened objects (packed)' ' |
| ( |
| cd max-cruft-size-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 '--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_done |