diff options
Diffstat (limited to '')
-rwxr-xr-x | t/t6500-gc.sh | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh new file mode 100755 index 0000000..18fe1c2 --- /dev/null +++ b/t/t6500-gc.sh @@ -0,0 +1,419 @@ +#!/bin/sh + +test_description='basic git gc tests +' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh + +test_expect_success 'setup' ' + # do not let the amount of physical memory affects gc + # behavior, make sure we always pack everything to one pack by + # default + git config gc.bigPackThreshold 2g && + + # These are simply values which, when hashed as a blob with a newline, + # produce a hash where the first byte is 0x17 in their respective + # algorithms. + test_oid_cache <<-EOF + obj1 sha1:263 + obj1 sha256:34 + + obj2 sha1:410 + obj2 sha256:174 + + obj3 sha1:523 + obj3 sha256:313 + + obj4 sha1:790 + obj4 sha256:481 + EOF +' + +test_expect_success 'gc empty repository' ' + git gc +' + +test_expect_success 'gc does not leave behind pid file' ' + git gc && + test_path_is_missing .git/gc.pid +' + +test_expect_success 'gc --gobbledegook' ' + test_expect_code 129 git gc --nonsense 2>err && + test_grep "[Uu]sage: git gc" err +' + +test_expect_success 'gc -h with invalid configuration' ' + mkdir broken && + ( + cd broken && + git init && + echo "[gc] pruneexpire = CORRUPT" >>.git/config && + test_expect_code 129 git gc -h >usage 2>&1 + ) && + test_grep "[Uu]sage" broken/usage +' + +test_expect_success 'gc is not aborted due to a stale symref' ' + git init remote && + ( + cd remote && + test_commit initial && + git clone . ../client && + git branch -m develop && + cd ../client && + git fetch --prune && + git gc + ) +' + +test_expect_success 'gc --keep-largest-pack' ' + test_create_repo keep-pack && + ( + cd keep-pack && + test_commit one && + test_commit two && + test_commit three && + git gc && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 1 pack-list && + cp pack-list base-pack-list && + test_commit four && + git repack -d && + test_commit five && + git repack -d && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 3 pack-list && + git gc --keep-largest-pack && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 2 pack-list && + awk "/^P /{print \$2}" <.git/objects/info/packs >pack-info && + test_line_count = 2 pack-info && + test_path_is_file .git/objects/pack/$(cat base-pack-list) && + git fsck + ) +' + +test_expect_success 'pre-auto-gc hook can stop auto gc' ' + cat >err.expect <<-\EOF && + no gc for you + EOF + + git init pre-auto-gc-hook && + test_hook -C pre-auto-gc-hook pre-auto-gc <<-\EOF && + echo >&2 no gc for you && + exit 1 + EOF + ( + cd pre-auto-gc-hook && + + git config gc.auto 3 && + git config gc.autoDetach false && + + # We need to create two object whose sha1s start with 17 + # since this is what git gc counts. As it happens, these + # two blobs will do so. + test_commit "$(test_oid obj1)" && + test_commit "$(test_oid obj2)" && + + git gc --auto >../out.actual 2>../err.actual + ) && + test_must_be_empty out.actual && + test_cmp err.expect err.actual && + + cat >err.expect <<-\EOF && + will gc for you + Auto packing the repository for optimum performance. + See "git help gc" for manual housekeeping. + EOF + + test_hook -C pre-auto-gc-hook --clobber pre-auto-gc <<-\EOF && + echo >&2 will gc for you && + exit 0 + EOF + + git -C pre-auto-gc-hook gc --auto >out.actual 2>err.actual && + + test_must_be_empty out.actual && + test_cmp err.expect err.actual +' + +test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' ' + test_config gc.auto 3 && + test_config gc.autodetach false && + test_config pack.writebitmaps true && + # We need to create two object whose sha1s start with 17 + # since this is what git gc counts. As it happens, these + # two blobs will do so. + test_commit "$(test_oid obj1)" && + test_commit "$(test_oid obj2)" && + # Our first gc will create a pack; our second will create a second pack + git gc --auto && + ls .git/objects/pack/pack-*.pack | sort >existing_packs && + test_commit "$(test_oid obj3)" && + test_commit "$(test_oid obj4)" && + + git gc --auto 2>err && + test_grep ! "^warning:" err && + ls .git/objects/pack/pack-*.pack | sort >post_packs && + comm -1 -3 existing_packs post_packs >new && + comm -2 -3 existing_packs post_packs >del && + test_line_count = 0 del && # No packs are deleted + test_line_count = 1 new # There is one new pack +' + +test_expect_success 'gc --no-quiet' ' + GIT_PROGRESS_DELAY=0 git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && + test_must_be_empty stdout && + test_grep "Computing commit graph generation numbers" stderr +' + +test_expect_success TTY 'with TTY: gc --no-quiet' ' + test_terminal env GIT_PROGRESS_DELAY=0 \ + git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && + test_must_be_empty stdout && + test_grep "Enumerating objects" stderr && + test_grep "Computing commit graph generation numbers" stderr +' + +test_expect_success 'gc --quiet' ' + git -c gc.writeCommitGraph=true gc --quiet >stdout 2>stderr && + test_must_be_empty stdout && + test_must_be_empty stderr +' + +test_expect_success 'gc.reflogExpire{Unreachable,}=never skips "expire" via "gc"' ' + test_config gc.reflogExpire never && + test_config gc.reflogExpireUnreachable never && + + GIT_TRACE=$(pwd)/trace.out git gc && + + # Check that git-pack-refs is run as a sanity check (done via + # gc_before_repack()) but that git-expire is not. + grep -E "^trace: (built-in|exec|run_command): git pack-refs --" trace.out && + ! grep -E "^trace: (built-in|exec|run_command): git reflog expire --" trace.out +' + +test_expect_success 'one of gc.reflogExpire{Unreachable,}=never does not skip "expire" via "gc"' ' + >trace.out && + test_config gc.reflogExpire never && + GIT_TRACE=$(pwd)/trace.out git gc && + grep -E "^trace: (built-in|exec|run_command): git reflog expire --" trace.out +' + +test_expect_success 'gc.repackFilter launches repack with a filter' ' + git clone --no-local --bare . bare.git && + + git -C bare.git -c gc.cruftPacks=false gc && + test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack && + + GIT_TRACE=$(pwd)/trace.out git -C bare.git -c gc.repackFilter=blob:none \ + -c repack.writeBitmaps=false -c gc.cruftPacks=false gc && + test_stdout_line_count = 2 ls bare.git/objects/pack/*.pack && + grep -E "^trace: (built-in|exec|run_command): git repack .* --filter=blob:none ?.*" trace.out +' + +test_expect_success 'gc.repackFilterTo store filtered out objects' ' + test_when_finished "rm -rf bare.git filtered.git" && + + git init --bare filtered.git && + git -C bare.git -c gc.repackFilter=blob:none \ + -c gc.repackFilterTo=../filtered.git/objects/pack/pack \ + -c repack.writeBitmaps=false -c gc.cruftPacks=false gc && + + test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack && + test_stdout_line_count = 1 ls filtered.git/objects/pack/*.pack +' + +prepare_cruft_history () { + test_commit base && + + test_commit --no-tag foo && + test_commit --no-tag bar && + git reset HEAD^^ +} + +assert_no_cruft_packs () { + find .git/objects/pack -name "*.mtimes" >mtimes && + test_must_be_empty mtimes +} + +for argv in \ + "gc" \ + "-c gc.cruftPacks=true gc" \ + "-c gc.cruftPacks=false gc --cruft" +do + test_expect_success "git $argv generates a cruft pack" ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + + prepare_cruft_history && + git $argv && + + find .git/objects/pack -name "*.mtimes" >mtimes && + sed -e 's/\.mtimes$/\.pack/g' mtimes >packs && + + test_file_not_empty packs && + while read pack + do + test_path_is_file "$pack" || return 1 + done <packs + ) + ' +done + +for argv in \ + "gc --no-cruft" \ + "-c gc.cruftPacks=false gc" \ + "-c gc.cruftPacks=true gc --no-cruft" +do + test_expect_success "git $argv does not generate a cruft pack" ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + + prepare_cruft_history && + git $argv && + + assert_no_cruft_packs + ) + ' +done + +test_expect_success '--keep-largest-pack ignores cruft packs' ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + + # Generate a pack for reachable objects (of which there + # are 3), and one for unreachable objects (of which + # there are 6). + prepare_cruft_history && + git gc --cruft && + + mtimes="$(find .git/objects/pack -type f -name "pack-*.mtimes")" && + sz="$(test_file_size "${mtimes%.mtimes}.pack")" && + + # Ensure that the cruft pack gets removed (due to + # `--prune=now`) despite it being the largest pack. + git -c gc.bigPackThreshold=$sz gc --cruft --prune=now && + + assert_no_cruft_packs + ) +' + +test_expect_success 'gc.bigPackThreshold ignores cruft packs' ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + + # Generate a pack for reachable objects (of which there + # are 3), and one for unreachable objects (of which + # there are 6). + prepare_cruft_history && + git gc --cruft && + + # Ensure that the cruft pack gets removed (due to + # `--prune=now`) despite it being the largest pack. + git gc --cruft --prune=now --keep-largest-pack && + + assert_no_cruft_packs + ) +' + +cruft_max_size_opts="git repack -d -l --cruft --cruft-expiration=2.weeks.ago" + +test_expect_success 'setup for --max-cruft-size tests' ' + git init cruft--max-size && + ( + cd cruft--max-size && + prepare_cruft_history + ) +' + +test_expect_success '--max-cruft-size sets appropriate repack options' ' + GIT_TRACE2_EVENT=$(pwd)/trace2.txt git -C cruft--max-size \ + gc --cruft --max-cruft-size=1M && + test_subcommand $cruft_max_size_opts --max-cruft-size=1048576 <trace2.txt +' + +test_expect_success 'gc.maxCruftSize sets appropriate repack options' ' + GIT_TRACE2_EVENT=$(pwd)/trace2.txt \ + git -C cruft--max-size -c gc.maxCruftSize=2M gc --cruft && + test_subcommand $cruft_max_size_opts --max-cruft-size=2097152 <trace2.txt && + + GIT_TRACE2_EVENT=$(pwd)/trace2.txt \ + git -C cruft--max-size -c gc.maxCruftSize=2M gc --cruft \ + --max-cruft-size=3M && + test_subcommand $cruft_max_size_opts --max-cruft-size=3145728 <trace2.txt +' + +run_and_wait_for_auto_gc () { + # We read stdout from gc for the side effect of waiting until the + # background gc process exits, closing its fd 9. Furthermore, the + # variable assignment from a command substitution preserves the + # exit status of the main gc process. + # Note: this fd trickery doesn't work on Windows, but there is no + # need to, because on Win the auto gc always runs in the foreground. + doesnt_matter=$(git gc --auto 9>&1) +} + +test_expect_success 'background auto gc does not run if gc.log is present and recent but does if it is old' ' + test_commit foo && + test_commit bar && + git repack && + test_config gc.autopacklimit 1 && + test_config gc.autodetach true && + echo fleem >.git/gc.log && + git gc --auto 2>err && + test_grep "^warning:" err && + test_config gc.logexpiry 5.days && + test-tool chmtime =-345600 .git/gc.log && + git gc --auto && + test_config gc.logexpiry 2.days && + run_and_wait_for_auto_gc && + ls .git/objects/pack/pack-*.pack >packs && + test_line_count = 1 packs +' + +test_expect_success 'background auto gc respects lock for all operations' ' + # make sure we run a background auto-gc + test_commit make-pack && + git repack && + test_config gc.autopacklimit 1 && + test_config gc.autodetach true && + + # create a ref whose loose presence we can use to detect a pack-refs run + git update-ref refs/heads/should-be-loose HEAD && + (ls -1 .git/refs/heads .git/reftable >expect || true) && + + # now fake a concurrent gc that holds the lock; we can use our + # shell pid so that it looks valid. + hostname=$(hostname || echo unknown) && + shell_pid=$$ && + if test_have_prereq MINGW && test -f /proc/$shell_pid/winpid + then + # In Git for Windows, Bash (actually, the MSYS2 runtime) has a + # different idea of PIDs than git.exe (actually Windows). Use + # the Windows PID in this case. + shell_pid=$(cat /proc/$shell_pid/winpid) + fi && + printf "%d %s" "$shell_pid" "$hostname" >.git/gc.pid && + + # our gc should exit zero without doing anything + run_and_wait_for_auto_gc && + (ls -1 .git/refs/heads .git/reftable >actual || true) && + test_cmp expect actual +' + +# DO NOT leave a detached auto gc process running near the end of the +# test script: it can run long enough in the background to racily +# interfere with the cleanup in 'test_done'. + +test_done |