#!/bin/sh # # Copyright (c) 2008 Johannes E. Schindelin # global test_description := ''prune'' source ./test-lib.sh global day := $(60*60*24) global week := $($day*7) proc add_blob { global before := $[git count-objects | sed "s/ .*//] && global BLOB := $[echo aleph_0 | git hash-object -w --stdin] && global BLOB_FILE := ".git/objects/$[echo $BLOB | sed "s/^../&\//]" && verbose test $(1 + $before) = $[git count-objects | sed "s/ .*//] && test_path_is_file $BLOB_FILE && test-chmtime =+0 $BLOB_FILE } test_expect_success setup ' : > file && git add file && test_tick && git commit -m initial && git gc ' test_expect_success 'prune stale packs' ' orig_pack=$(echo .git/objects/pack/*.pack) && : > .git/objects/tmp_1.pack && : > .git/objects/tmp_2.pack && test-chmtime =-86501 .git/objects/tmp_1.pack && git prune --expire 1.day && test_path_is_file $orig_pack && test_path_is_file .git/objects/tmp_2.pack && test_path_is_missing .git/objects/tmp_1.pack ' test_expect_success 'prune --expire' ' add_blob && git prune --expire=1.hour.ago && verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE && test-chmtime =-86500 $BLOB_FILE && git prune --expire 1.day && verbose test $before = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE ' test_expect_success 'gc: implicit prune --expire' ' add_blob && test-chmtime =-$((2*$week-30)) $BLOB_FILE && git gc && verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE && test-chmtime =-$((2*$week+1)) $BLOB_FILE && git gc && verbose test $before = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE ' test_expect_success 'gc: refuse to start with invalid gc.pruneExpire' ' git config gc.pruneExpire invalid && test_must_fail git gc ' test_expect_success 'gc: start with ok gc.pruneExpire' ' git config gc.pruneExpire 2.days.ago && git gc ' test_expect_success 'prune: prune nonsense parameters' ' test_must_fail git prune garbage && test_must_fail git prune --- && test_must_fail git prune --no-such-option ' test_expect_success 'prune: prune unreachable heads' ' git config core.logAllRefUpdates false && mv .git/logs .git/logs.old && : > file2 && git add file2 && git commit -m temporary && tmp_head=$(git rev-list -1 HEAD) && git reset HEAD^ && git prune && test_must_fail git reset $tmp_head -- ' test_expect_success 'prune: do not prune detached HEAD with no reflog' ' git checkout --detach --quiet && git commit --allow-empty -m "detached commit" && # verify that there is no reflogs # (should be removed and disabled by previous test) test_path_is_missing .git/logs && git prune -n >prune_actual && : >prune_expected && test_cmp prune_actual prune_expected ' test_expect_success 'prune: prune former HEAD after checking out branch' ' head_sha1=$(git rev-parse HEAD) && git checkout --quiet master && git prune -v >prune_actual && grep "$head_sha1" prune_actual ' test_expect_success 'prune: do not prune heads listed as an argument' ' : > file2 && git add file2 && git commit -m temporary && tmp_head=$(git rev-list -1 HEAD) && git reset HEAD^ && git prune -- $tmp_head && git reset $tmp_head -- ' test_expect_success 'gc --no-prune' ' add_blob && test-chmtime =-$((5001*$day)) $BLOB_FILE && git config gc.pruneExpire 2.days.ago && git gc --no-prune && verbose test 1 = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE ' test_expect_success 'gc respects gc.pruneExpire' ' git config gc.pruneExpire 5002.days.ago && git gc && test_path_is_file $BLOB_FILE && git config gc.pruneExpire 5000.days.ago && git gc && test_path_is_missing $BLOB_FILE ' test_expect_success 'gc --prune=' ' add_blob && test-chmtime =-$((5001*$day)) $BLOB_FILE && git gc --prune=5002.days.ago && test_path_is_file $BLOB_FILE && git gc --prune=5000.days.ago && test_path_is_missing $BLOB_FILE ' test_expect_success 'gc --prune=never' ' add_blob && git gc --prune=never && test_path_is_file $BLOB_FILE && git gc --prune=now && test_path_is_missing $BLOB_FILE ' test_expect_success 'gc respects gc.pruneExpire=never' ' git config gc.pruneExpire never && add_blob && git gc && test_path_is_file $BLOB_FILE && git config gc.pruneExpire now && git gc && test_path_is_missing $BLOB_FILE ' test_expect_success 'prune --expire=never' ' add_blob && git prune --expire=never && test_path_is_file $BLOB_FILE && git prune && test_path_is_missing $BLOB_FILE ' test_expect_success 'gc: prune old objects after local clone' ' add_blob && test-chmtime =-$((2*$week+1)) $BLOB_FILE && git clone --no-hardlinks . aclone && ( cd aclone && verbose test 1 = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE && git gc --prune && verbose test 0 = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE ) ' test_expect_success 'garbage report in count-objects -v' ' test_when_finished "rm -f .git/objects/pack/fake*" && test_when_finished "rm -f .git/objects/pack/foo*" && : >.git/objects/pack/foo && : >.git/objects/pack/foo.bar && : >.git/objects/pack/foo.keep && : >.git/objects/pack/foo.pack && : >.git/objects/pack/fake.bar && : >.git/objects/pack/fake.keep && : >.git/objects/pack/fake.pack && : >.git/objects/pack/fake.idx && : >.git/objects/pack/fake2.keep && : >.git/objects/pack/fake3.idx && git count-objects -v 2>stderr && grep "index file .git/objects/pack/fake.idx is too small" stderr && grep "^warning:" stderr | sort >actual && cat >expected <<\EOF && warning: garbage found: .git/objects/pack/fake.bar warning: garbage found: .git/objects/pack/foo warning: garbage found: .git/objects/pack/foo.bar warning: no corresponding .idx or .pack: .git/objects/pack/fake2.keep warning: no corresponding .idx: .git/objects/pack/foo.keep warning: no corresponding .idx: .git/objects/pack/foo.pack warning: no corresponding .pack: .git/objects/pack/fake3.idx EOF test_cmp expected actual ' test_expect_success 'clean pack garbage with gc' ' test_when_finished "rm -f .git/objects/pack/fake*" && test_when_finished "rm -f .git/objects/pack/foo*" && : >.git/objects/pack/foo.keep && : >.git/objects/pack/foo.pack && : >.git/objects/pack/fake.idx && : >.git/objects/pack/fake2.keep && : >.git/objects/pack/fake2.idx && : >.git/objects/pack/fake3.keep && git gc && git count-objects -v 2>stderr && grep "^warning:" stderr | sort >actual && cat >expected <<\EOF && warning: no corresponding .idx or .pack: .git/objects/pack/fake3.keep warning: no corresponding .idx: .git/objects/pack/foo.keep warning: no corresponding .idx: .git/objects/pack/foo.pack EOF test_cmp expected actual ' test_expect_success 'prune .git/shallow' ' SHA1=$(echo hi|git commit-tree HEAD^{tree}) && echo $SHA1 >.git/shallow && git prune --dry-run >out && grep $SHA1 .git/shallow && grep $SHA1 out && git prune && test_path_is_missing .git/shallow ' test_expect_success 'prune: handle alternate object database' ' test_create_repo A && git -C A commit --allow-empty -m "initial commit" && git clone --shared A B && git -C B commit --allow-empty -m "next commit" && git -C B prune ' test_done (CommandList children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:test_description) op: Equal rhs: {(SQ )} spids: [13] ) ] spids: [13] ) (C {(.)} {(./test-lib.sh)}) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:day) op: Equal rhs: { (ArithSubPart anode: (ArithBinary op_id: Arith_Star left: (ArithBinary op_id: Arith_Star left: (ArithWord w:{(Lit_Digits 60)}) right: (ArithWord w:{(Lit_Digits 60)}) ) right: (ArithWord w:{(Lit_Digits 24)}) ) spids: [24 31] ) } spids: [23] ) ] spids: [23] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:week) op: Equal rhs: { (ArithSubPart anode: (ArithBinary op_id: Arith_Star left: (ArithWord w:{($ VSub_Name "$day")}) right: (ArithWord w:{(Lit_Digits 7)}) ) spids: [34 39] ) } spids: [33] ) ] spids: [33] ) (FuncDef name: add_blob body: (BraceGroup children: [ (AndOr children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:before) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(git)} {(count-objects)}) (C {(sed)} {(DQ ("s/ .*//"))}) ] negated: False ) ] ) left_token: spids: [50 62] ) } spids: [49] ) ] spids: [49] ) (AndOr children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:BLOB) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {(aleph_0)}) (C {(git)} {(hash-object)} {(-w)} {(--stdin)}) ] negated: False ) ] ) left_token: spids: [68 82] ) } spids: [67] ) ] spids: [67] ) (AndOr children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:BLOB_FILE) op: Equal rhs: {(.git/objects/) (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {($ VSub_Name "$BLOB")}) (C {(sed)} { (DQ ("s/^../&") (EscapedLiteralPart token: ) (/) ) } ) ] negated: False ) ] ) left_token: spids: [89 103] ) } spids: [87] ) ] spids: [87] ) (AndOr children: [ (C {(verbose)} {(test)} { (ArithSubPart anode: (ArithBinary op_id: Arith_Plus left: (ArithWord w:{(Lit_Digits 1)}) right: (ArithWord w:{($ VSub_Name "$before")}) ) spids: [112 119] ) } {(Lit_Other "=")} { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(git)} {(count-objects)}) (C {(sed)} {(DQ ("s/ .*//"))}) ] negated: False ) ] ) left_token: spids: [123 135] ) } ) (AndOr children: [ (C {(test_path_is_file)} {($ VSub_Name "$BLOB_FILE")}) (C {(test-chmtime)} {(Lit_Other "=") (Lit_Other "+") (0)} {($ VSub_Name "$BLOB_FILE")} ) ] op_id: Op_DAmp ) ] op_id: Op_DAmp ) ] op_id: Op_DAmp ) ] op_id: Op_DAmp ) ] op_id: Op_DAmp ) ] spids: [46] ) spids: [42 45] ) (C {(test_expect_success)} {(setup)} { (SQ <"\n"> <"\n"> <"\t: > file &&\n"> <"\tgit add file &&\n"> <"\ttest_tick &&\n"> <"\tgit commit -m initial &&\n"> <"\tgit gc\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune stale packs">)} { (SQ <"\n"> <"\n"> <"\torig_pack=$(echo .git/objects/pack/*.pack) &&\n"> <"\t: > .git/objects/tmp_1.pack &&\n"> <"\t: > .git/objects/tmp_2.pack &&\n"> <"\ttest-chmtime =-86501 .git/objects/tmp_1.pack &&\n"> <"\tgit prune --expire 1.day &&\n"> <"\ttest_path_is_file $orig_pack &&\n"> <"\ttest_path_is_file .git/objects/tmp_2.pack &&\n"> <"\ttest_path_is_missing .git/objects/tmp_1.pack\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune --expire">)} { (SQ <"\n"> <"\n"> <"\tadd_blob &&\n"> <"\tgit prune --expire=1.hour.ago &&\n"> <"\tverbose test $((1 + $before)) = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\ttest-chmtime =-86500 $BLOB_FILE &&\n"> <"\tgit prune --expire 1.day &&\n"> <"\tverbose test $before = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc: implicit prune --expire">)} { (SQ <"\n"> <"\n"> <"\tadd_blob &&\n"> <"\ttest-chmtime =-$((2*$week-30)) $BLOB_FILE &&\n"> <"\tgit gc &&\n"> <"\tverbose test $((1 + $before)) = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\ttest-chmtime =-$((2*$week+1)) $BLOB_FILE &&\n"> <"\tgit gc &&\n"> <"\tverbose test $before = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc: refuse to start with invalid gc.pruneExpire">)} { (SQ <"\n"> <"\n"> <"\tgit config gc.pruneExpire invalid &&\n"> <"\ttest_must_fail git gc\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc: start with ok gc.pruneExpire">)} {(SQ <"\n"> <"\n"> <"\tgit config gc.pruneExpire 2.days.ago &&\n"> <"\tgit gc\n"> <"\n">)} ) (C {(test_expect_success)} {(SQ <"prune: prune nonsense parameters">)} { (SQ <"\n"> <"\n"> <"\ttest_must_fail git prune garbage &&\n"> <"\ttest_must_fail git prune --- &&\n"> <"\ttest_must_fail git prune --no-such-option\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune: prune unreachable heads">)} { (SQ <"\n"> <"\n"> <"\tgit config core.logAllRefUpdates false &&\n"> <"\tmv .git/logs .git/logs.old &&\n"> <"\t: > file2 &&\n"> <"\tgit add file2 &&\n"> <"\tgit commit -m temporary &&\n"> <"\ttmp_head=$(git rev-list -1 HEAD) &&\n"> <"\tgit reset HEAD^ &&\n"> <"\tgit prune &&\n"> <"\ttest_must_fail git reset $tmp_head --\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune: do not prune detached HEAD with no reflog">)} { (SQ <"\n"> <"\n"> <"\tgit checkout --detach --quiet &&\n"> <"\tgit commit --allow-empty -m \"detached commit\" &&\n"> <"\t# verify that there is no reflogs\n"> <"\t# (should be removed and disabled by previous test)\n"> <"\ttest_path_is_missing .git/logs &&\n"> <"\tgit prune -n >prune_actual &&\n"> <"\t: >prune_expected &&\n"> <"\ttest_cmp prune_actual prune_expected\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune: prune former HEAD after checking out branch">)} { (SQ <"\n"> <"\n"> <"\thead_sha1=$(git rev-parse HEAD) &&\n"> <"\tgit checkout --quiet master &&\n"> <"\tgit prune -v >prune_actual &&\n"> <"\tgrep \"$head_sha1\" prune_actual\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune: do not prune heads listed as an argument">)} { (SQ <"\n"> <"\n"> <"\t: > file2 &&\n"> <"\tgit add file2 &&\n"> <"\tgit commit -m temporary &&\n"> <"\ttmp_head=$(git rev-list -1 HEAD) &&\n"> <"\tgit reset HEAD^ &&\n"> <"\tgit prune -- $tmp_head &&\n"> <"\tgit reset $tmp_head --\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc --no-prune">)} { (SQ <"\n"> <"\n"> <"\tadd_blob &&\n"> <"\ttest-chmtime =-$((5001*$day)) $BLOB_FILE &&\n"> <"\tgit config gc.pruneExpire 2.days.ago &&\n"> <"\tgit gc --no-prune &&\n"> <"\tverbose test 1 = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\ttest_path_is_file $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc respects gc.pruneExpire">)} { (SQ <"\n"> <"\n"> <"\tgit config gc.pruneExpire 5002.days.ago &&\n"> <"\tgit gc &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\tgit config gc.pruneExpire 5000.days.ago &&\n"> <"\tgit gc &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc --prune=">)} { (SQ <"\n"> <"\n"> <"\tadd_blob &&\n"> <"\ttest-chmtime =-$((5001*$day)) $BLOB_FILE &&\n"> <"\tgit gc --prune=5002.days.ago &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\tgit gc --prune=5000.days.ago &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc --prune=never">)} { (SQ <"\n"> <"\n"> <"\tadd_blob &&\n"> <"\tgit gc --prune=never &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\tgit gc --prune=now &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc respects gc.pruneExpire=never">)} { (SQ <"\n"> <"\n"> <"\tgit config gc.pruneExpire never &&\n"> <"\tadd_blob &&\n"> <"\tgit gc &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\tgit config gc.pruneExpire now &&\n"> <"\tgit gc &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune --expire=never">)} { (SQ <"\n"> <"\n"> <"\tadd_blob &&\n"> <"\tgit prune --expire=never &&\n"> <"\ttest_path_is_file $BLOB_FILE &&\n"> <"\tgit prune &&\n"> <"\ttest_path_is_missing $BLOB_FILE\n"> <"\n"> ) } ) (C {(test_expect_success)} {(SQ <"gc: prune old objects after local clone">)} { (SQ <"\n"> <"\tadd_blob &&\n"> <"\ttest-chmtime =-$((2*$week+1)) $BLOB_FILE &&\n"> <"\tgit clone --no-hardlinks . aclone &&\n"> <"\t(\n"> <"\t\tcd aclone &&\n"> <"\t\tverbose test 1 = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\t\ttest_path_is_file $BLOB_FILE &&\n"> <"\t\tgit gc --prune &&\n"> <"\t\tverbose test 0 = $(git count-objects | sed \"s/ .*//\") &&\n"> <"\t\ttest_path_is_missing $BLOB_FILE\n"> <"\t)\n"> ) } ) (C {(test_expect_success)} {(SQ <"garbage report in count-objects -v">)} { (SQ <"\n"> <"\ttest_when_finished \"rm -f .git/objects/pack/fake*\" &&\n"> <"\ttest_when_finished \"rm -f .git/objects/pack/foo*\" &&\n"> <"\t: >.git/objects/pack/foo &&\n"> <"\t: >.git/objects/pack/foo.bar &&\n"> <"\t: >.git/objects/pack/foo.keep &&\n"> <"\t: >.git/objects/pack/foo.pack &&\n"> <"\t: >.git/objects/pack/fake.bar &&\n"> <"\t: >.git/objects/pack/fake.keep &&\n"> <"\t: >.git/objects/pack/fake.pack &&\n"> <"\t: >.git/objects/pack/fake.idx &&\n"> <"\t: >.git/objects/pack/fake2.keep &&\n"> <"\t: >.git/objects/pack/fake3.idx &&\n"> <"\tgit count-objects -v 2>stderr &&\n"> <"\tgrep \"index file .git/objects/pack/fake.idx is too small\" stderr &&\n"> <"\tgrep \"^warning:\" stderr | sort >actual &&\n"> <"\tcat >expected <<\\EOF &&\n"> <"warning: garbage found: .git/objects/pack/fake.bar\n"> <"warning: garbage found: .git/objects/pack/foo\n"> <"warning: garbage found: .git/objects/pack/foo.bar\n"> <"warning: no corresponding .idx or .pack: .git/objects/pack/fake2.keep\n"> <"warning: no corresponding .idx: .git/objects/pack/foo.keep\n"> <"warning: no corresponding .idx: .git/objects/pack/foo.pack\n"> <"warning: no corresponding .pack: .git/objects/pack/fake3.idx\n"> <"EOF\n"> <"\ttest_cmp expected actual\n"> ) } ) (C {(test_expect_success)} {(SQ <"clean pack garbage with gc">)} { (SQ <"\n"> <"\ttest_when_finished \"rm -f .git/objects/pack/fake*\" &&\n"> <"\ttest_when_finished \"rm -f .git/objects/pack/foo*\" &&\n"> <"\t: >.git/objects/pack/foo.keep &&\n"> <"\t: >.git/objects/pack/foo.pack &&\n"> <"\t: >.git/objects/pack/fake.idx &&\n"> <"\t: >.git/objects/pack/fake2.keep &&\n"> <"\t: >.git/objects/pack/fake2.idx &&\n"> <"\t: >.git/objects/pack/fake3.keep &&\n"> <"\tgit gc &&\n"> <"\tgit count-objects -v 2>stderr &&\n"> <"\tgrep \"^warning:\" stderr | sort >actual &&\n"> <"\tcat >expected <<\\EOF &&\n"> <"warning: no corresponding .idx or .pack: .git/objects/pack/fake3.keep\n"> <"warning: no corresponding .idx: .git/objects/pack/foo.keep\n"> <"warning: no corresponding .idx: .git/objects/pack/foo.pack\n"> <"EOF\n"> <"\ttest_cmp expected actual\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune .git/shallow">)} { (SQ <"\n"> <"\tSHA1=$(echo hi|git commit-tree HEAD^{tree}) &&\n"> <"\techo $SHA1 >.git/shallow &&\n"> <"\tgit prune --dry-run >out &&\n"> <"\tgrep $SHA1 .git/shallow &&\n"> <"\tgrep $SHA1 out &&\n"> <"\tgit prune &&\n"> <"\ttest_path_is_missing .git/shallow\n"> ) } ) (C {(test_expect_success)} {(SQ <"prune: handle alternate object database">)} { (SQ <"\n"> <"\ttest_create_repo A &&\n"> <"\tgit -C A commit --allow-empty -m \"initial commit\" &&\n"> <"\tgit clone --shared A B &&\n"> <"\tgit -C B commit --allow-empty -m \"next commit\" &&\n"> <"\tgit -C B prune\n"> ) } ) (C {(test_done)}) ] )