3 test_description
='cruft pack related pack-objects tests'
9 basic_cruft_pack_tests
() {
12 test_expect_success
"unreachable loose objects are packed (expire $expire)" '
14 test_when_finished "rm -fr repo" &&
22 test-tool chmtime +2000 "$objdir/$(test_oid_to_path \
23 $(git rev-parse loose:loose.t))" &&
24 test-tool chmtime +1000 "$objdir/$(test_oid_to_path \
25 $(git rev-parse loose^{tree}))" &&
28 git rev-list --objects --no-object-names base..loose |
31 path="$objdir/$(test_oid_to_path "$oid")" &&
32 printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" ||
33 echo "object list generation failed for $oid"
38 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
39 cruft="$(echo $keep | git pack-objects --cruft \
40 --cruft-expiration="$expire" $packdir/pack)" &&
41 test-tool pack-mtimes "pack-$cruft.mtimes" >actual &&
43 test_cmp expect actual
47 test_expect_success
"unreachable packed objects are packed (expire $expire)" '
49 test_when_finished "rm -fr repo" &&
57 git rev-list --objects --no-object-names packed.. >objects &&
58 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
59 other="$(git pack-objects --delta-base-offset \
60 $packdir/pack <objects)" &&
63 test-tool chmtime --get -100 "$packdir/pack-$other.pack" >expect &&
65 cruft="$(git pack-objects --cruft --cruft-expiration="$expire" $packdir/pack <<-EOF
70 test-tool pack-mtimes "pack-$cruft.mtimes" >actual.raw &&
72 cut -d" " -f2 <actual.raw | sort -u >actual &&
74 test_cmp expect actual
78 test_expect_success
"unreachable cruft objects are repacked (expire $expire)" '
80 test_when_finished "rm -fr repo" &&
88 git rev-list --objects --no-object-names packed.. >objects &&
89 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
91 cruft_a="$(echo $keep | git pack-objects --cruft --cruft-expiration="$expire" $packdir/pack)" &&
93 cruft_b="$(git pack-objects --cruft --cruft-expiration="$expire" $packdir/pack <<-EOF
99 test-tool pack-mtimes "pack-$cruft_a.mtimes" >expect.raw &&
100 test-tool pack-mtimes "pack-$cruft_b.mtimes" >actual.raw &&
102 sort <expect.raw >expect &&
103 sort <actual.raw >actual &&
105 test_cmp expect actual
109 test_expect_success
"multiple cruft packs (expire $expire)" '
111 test_when_finished "rm -fr repo" &&
115 test_commit reachable &&
117 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
120 loose="$objdir/$(test_oid_to_path $(git rev-parse cruft))" &&
122 # generate three copies of the cruft object in different
123 # cruft packs, each with a unique mtime:
124 # - one expired (1000 seconds ago)
125 # - two non-expired (one 1000 seconds in the future,
126 # one 1500 seconds in the future)
127 test-tool chmtime =-1000 "$loose" &&
128 git pack-objects --cruft $packdir/pack-A <<-EOF &&
131 test-tool chmtime =+1000 "$loose" &&
132 git pack-objects --cruft $packdir/pack-B <<-EOF &&
134 -$(basename $(ls $packdir/pack-A-*.pack))
136 test-tool chmtime =+1500 "$loose" &&
137 git pack-objects --cruft $packdir/pack-C <<-EOF &&
139 -$(basename $(ls $packdir/pack-A-*.pack))
140 -$(basename $(ls $packdir/pack-B-*.pack))
143 # ensure the resulting cruft pack takes the most recent
144 # mtime among all copies
145 cruft="$(git pack-objects --cruft \
146 --cruft-expiration="$expire" \
149 -$(basename $(ls $packdir/pack-A-*.pack))
150 -$(basename $(ls $packdir/pack-B-*.pack))
151 -$(basename $(ls $packdir/pack-C-*.pack))
155 test-tool pack-mtimes "$(basename $(ls $packdir/pack-C-*.mtimes))" >expect.raw &&
156 test-tool pack-mtimes "pack-$cruft.mtimes" >actual.raw &&
158 sort expect.raw >expect &&
159 sort actual.raw >actual &&
160 test_cmp expect actual
164 test_expect_success
"cruft packs tolerate missing trees (expire $expire)" '
166 test_when_finished "rm -fr repo" &&
170 test_commit reachable &&
173 tree="$(git rev-parse cruft^{tree})" &&
175 git reset --hard reachable &&
177 git reflog expire --all --expire=all &&
179 # remove the unreachable tree, but leave the commit
180 # which has it as its root tree intact
181 rm -fr "$objdir/$(test_oid_to_path "$tree")" &&
184 basename $(ls $packdir/pack-*.pack) >in &&
185 git pack-objects --cruft --cruft-expiration="$expire" \
190 test_expect_success
"cruft packs tolerate missing blobs (expire $expire)" '
192 test_when_finished "rm -fr repo" &&
196 test_commit reachable &&
199 blob="$(git rev-parse cruft:cruft.t)" &&
201 git reset --hard reachable &&
203 git reflog expire --all --expire=all &&
205 # remove the unreachable blob, but leave the commit (and
206 # the root tree of that commit) intact
207 rm -fr "$objdir/$(test_oid_to_path "$blob")" &&
210 basename $(ls $packdir/pack-*.pack) >in &&
211 git pack-objects --cruft --cruft-expiration="$expire" \
217 basic_cruft_pack_tests never
218 basic_cruft_pack_tests
2.weeks.ago
220 test_expect_success
'cruft tags rescue tagged objects' '
222 test_when_finished "rm -fr repo" &&
226 test_commit packed &&
229 test_commit tagged &&
230 git tag -a annotated -m tag &&
232 git rev-list --objects --no-object-names packed.. >objects &&
235 test-tool chmtime -1000 \
236 "$objdir/$(test_oid_to_path $oid)" || exit 1
239 test-tool chmtime -500 \
240 "$objdir/$(test_oid_to_path $(git rev-parse annotated))" &&
242 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
243 cruft="$(echo $keep | git pack-objects --cruft \
244 --cruft-expiration=750.seconds.ago \
246 test-tool pack-mtimes "pack-$cruft.mtimes" >actual.raw &&
247 cut -f1 -d" " <actual.raw | sort >actual &&
251 git rev-parse annotated
253 sort <expect.raw >expect &&
255 test_cmp expect actual &&
260 test_expect_success
'cruft commits rescue parents, trees' '
262 test_when_finished "rm -fr repo" &&
266 test_commit packed &&
272 git rev-list --objects --no-object-names packed..new >objects &&
275 test-tool chmtime -1000 \
276 "$objdir/$(test_oid_to_path $object)" || exit 1
278 test-tool chmtime +500 "$objdir/$(test_oid_to_path \
279 $(git rev-parse HEAD))" &&
281 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
282 cruft="$(echo $keep | git pack-objects --cruft \
283 --cruft-expiration=750.seconds.ago \
285 test-tool pack-mtimes "pack-$cruft.mtimes" >actual.raw &&
287 cut -d" " -f1 <actual.raw | sort >actual &&
288 sort <objects >expect &&
290 test_cmp expect actual
294 test_expect_success
'cruft trees rescue sub-trees, blobs' '
296 test_when_finished "rm -fr repo" &&
300 test_commit packed &&
306 echo baz >dir/sub/baz &&
310 git commit -m "pruned" &&
312 test-tool chmtime -1000 "$objdir/$(test_oid_to_path $(git rev-parse HEAD))" &&
313 test-tool chmtime -1000 "$objdir/$(test_oid_to_path $(git rev-parse HEAD^{tree}))" &&
314 test-tool chmtime -1000 "$objdir/$(test_oid_to_path $(git rev-parse HEAD:foo))" &&
315 test-tool chmtime -500 "$objdir/$(test_oid_to_path $(git rev-parse HEAD:dir))" &&
316 test-tool chmtime -1000 "$objdir/$(test_oid_to_path $(git rev-parse HEAD:dir/bar))" &&
317 test-tool chmtime -1000 "$objdir/$(test_oid_to_path $(git rev-parse HEAD:dir/sub))" &&
318 test-tool chmtime -1000 "$objdir/$(test_oid_to_path $(git rev-parse HEAD:dir/sub/baz))" &&
320 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
321 cruft="$(echo $keep | git pack-objects --cruft \
322 --cruft-expiration=750.seconds.ago \
324 test-tool pack-mtimes "pack-$cruft.mtimes" >actual.raw &&
325 cut -f1 -d" " <actual.raw | sort >actual &&
327 git rev-parse HEAD:dir HEAD:dir/bar HEAD:dir/sub HEAD:dir/sub/baz >expect.raw &&
328 sort <expect.raw >expect &&
330 test_cmp expect actual
334 test_expect_success
'expired objects are pruned' '
336 test_when_finished "rm -fr repo" &&
340 test_commit packed &&
343 test_commit pruned &&
345 git rev-list --objects --no-object-names packed..pruned >objects &&
348 test-tool chmtime -1000 \
349 "$objdir/$(test_oid_to_path $object)" || exit 1
352 keep="$(basename "$(ls $packdir/pack-*.pack)")" &&
353 cruft="$(echo $keep | git pack-objects --cruft \
354 --cruft-expiration=750.seconds.ago \
357 test-tool pack-mtimes "pack-$cruft.mtimes" >actual &&
358 test_must_be_empty actual
362 test_expect_success
'repack --cruft generates a cruft pack' '
364 test_when_finished "rm -fr repo" &&
368 test_commit reachable &&
369 git branch -M main &&
370 git checkout --orphan other &&
371 test_commit unreachable &&
374 git branch -D other &&
375 git tag -d unreachable &&
376 # objects are not cruft if they are contained in the reflogs
377 git reflog expire --all --expire=all &&
379 git rev-list --objects --all --no-object-names >reachable.raw &&
380 git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
381 sort <reachable.raw >reachable &&
382 comm -13 reachable objects >unreachable &&
384 git repack --cruft -d &&
386 cruft=$(basename $(ls $packdir/pack-*.mtimes) .mtimes) &&
387 pack=$(basename $(ls $packdir/pack-*.pack | grep -v $cruft) .pack) &&
389 git show-index <$packdir/$pack.idx >actual.raw &&
390 cut -f2 -d" " actual.raw | sort >actual &&
391 test_cmp reachable actual &&
393 git show-index <$packdir/$cruft.idx >actual.raw &&
394 cut -f2 -d" " actual.raw | sort >actual &&
395 test_cmp unreachable actual
399 test_expect_success
'loose objects mtimes upsert others' '
401 test_when_finished "rm -fr repo" &&
405 test_commit reachable &&
407 git branch -M main &&
409 git checkout --orphan other &&
411 # incremental repack, leaving existing objects loose (so
412 # they can be "freshened")
415 tip="$(git rev-parse cruft)" &&
416 path="$objdir/$(test_oid_to_path "$tip")" &&
417 test-tool chmtime --get +1000 "$path" >expect &&
420 git branch -D other &&
422 git reflog expire --all --expire=all &&
424 git repack --cruft -d &&
426 mtimes="$(basename $(ls $packdir/pack-*.mtimes))" &&
427 test-tool pack-mtimes "$mtimes" >actual.raw &&
428 grep "$tip" actual.raw | cut -d" " -f2 >actual &&
429 test_cmp expect actual
433 test_expect_success
'expiring cruft objects with git gc' '
435 test_when_finished "rm -fr repo" &&
439 test_commit reachable &&
440 git branch -M main &&
441 git checkout --orphan other &&
442 test_commit unreachable &&
445 git branch -D other &&
446 git tag -d unreachable &&
447 # objects are not cruft if they are contained in the reflogs
448 git reflog expire --all --expire=all &&
450 git rev-list --objects --all --no-object-names >reachable.raw &&
451 git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
452 sort <reachable.raw >reachable &&
453 comm -13 reachable objects >unreachable &&
455 # Write a cruft pack containing all unreachable objects.
456 git gc --cruft --prune="01-01-1980" &&
458 mtimes=$(ls .git/objects/pack/pack-*.mtimes) &&
459 test_path_is_file $mtimes &&
461 # Prune all unreachable objects from the cruft pack.
462 git gc --cruft --prune=now &&
464 git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
466 comm -23 unreachable objects >removed &&
467 test_cmp unreachable removed &&
468 test_path_is_missing $mtimes
472 test_expect_success
'cruft packs are not included in geometric repack' '
474 test_when_finished "rm -fr repo" &&
478 test_commit reachable &&
480 git branch -M main &&
482 git checkout --orphan other &&
487 git branch -D other &&
489 git reflog expire --all --expire=all &&
491 git repack --cruft &&
493 find $packdir -type f | sort >before &&
494 git repack --geometric=2 -d &&
495 find $packdir -type f | sort >after &&
497 test_cmp before after
501 test_expect_success
'repack --geometric collects once-cruft objects' '
503 test_when_finished "rm -fr repo" &&
507 test_commit reachable &&
509 git branch -M main &&
511 git checkout --orphan other &&
513 test_commit --no-tag cruft &&
514 cruft="$(git rev-parse HEAD)" &&
517 git branch -D other &&
518 git reflog expire --all --expire=all &&
520 # Pack the objects created in the previous step into a cruft
521 # pack. Intentionally leave loose copies of those objects
522 # around so we can pick them up in a subsequent --geometric
524 git repack --cruft &&
526 # Now make those objects reachable, and ensure that they are
527 # packed into the new pack created via a --geometric repack.
528 git update-ref refs/heads/other $cruft &&
530 # Without this object, the set of unpacked objects is exactly
531 # the set of objects already in the cruft pack. Tweak that set
532 # to ensure we do not overwrite the cruft pack entirely.
533 test_commit reachable2 &&
535 find $packdir -name "pack-*.idx" | sort >before &&
536 git repack --geometric=2 -d &&
537 find $packdir -name "pack-*.idx" | sort >after &&
540 git rev-list --objects --no-object-names $cruft &&
541 git rev-list --objects --no-object-names reachable..reachable2
543 sort want.raw >want &&
545 pack=$(comm -13 before after) &&
546 git show-index <$pack >objects.raw &&
548 cut -d" " -f2 objects.raw | sort >got &&
554 test_expect_success
'cruft repack with no reachable objects' '
556 test_when_finished "rm -fr repo" &&
563 base="$(git rev-parse base)" &&
565 git for-each-ref --format="delete %(refname)" >in &&
566 git update-ref --stdin <in &&
567 git reflog expire --all --expire=all &&
570 git repack --cruft -d &&
572 git cat-file -t $base
576 test_expect_success
'cruft repack ignores --max-pack-size' '
577 git init max-pack-size &&
581 # two cruft objects which exceed the maximum pack size
582 test-tool genrandom foo 1048576 | git hash-object --stdin -w &&
583 test-tool genrandom bar 1048576 | git hash-object --stdin -w &&
584 git repack --cruft --max-pack-size=1M &&
585 find $packdir -name "*.mtimes" >cruft &&
586 test_line_count = 1 cruft &&
587 test-tool pack-mtimes "$(basename "$(cat cruft)")" >objects &&
588 test_line_count = 2 objects
592 test_expect_success
'cruft repack ignores pack.packSizeLimit' '
595 # repack everything back together to remove the existing cruft
596 # pack (but to keep its objects)
598 git -c pack.packSizeLimit=1M repack --cruft &&
599 # ensure the same post condition is met when --max-pack-size
600 # would otherwise be inferred from the configuration
601 find $packdir -name "*.mtimes" >cruft &&
602 test_line_count = 1 cruft &&
603 test-tool pack-mtimes "$(basename "$(cat cruft)")" >objects &&
604 test_line_count = 2 objects
608 test_expect_success
'cruft repack respects repack.cruftWindow' '
610 test_when_finished "rm -fr repo" &&
616 GIT_TRACE2_EVENT=$(pwd)/event.trace \
617 git -c pack.window=1 -c repack.cruftWindow=2 repack \
618 --cruft --window=3 &&
620 grep "pack-objects.*--window=2.*--cruft" event.trace
624 test_expect_success
'cruft repack respects --window by default' '
626 test_when_finished "rm -fr repo" &&
632 GIT_TRACE2_EVENT=$(pwd)/event.trace \
633 git -c pack.window=2 repack --cruft --window=3 &&
635 grep "pack-objects.*--window=3.*--cruft" event.trace
639 test_expect_success
'cruft repack respects --quiet' '
641 test_when_finished "rm -fr repo" &&
646 GIT_PROGRESS_DELAY=0 git repack --cruft --quiet 2>err &&
647 test_must_be_empty err
651 test_expect_success
'cruft --local drops unreachable objects' '
652 git init alternate &&
654 test_when_finished "rm -fr alternate repo" &&
656 test_commit -C alternate base &&
657 # Pack all objects in alterate so that the cruft repack in "repo" sees
658 # the object it dropped due to `--local` as packed. Otherwise this
659 # object would not appear packed anywhere (since it is not packed in
660 # alternate and likewise not part of the cruft pack in the other repo
661 # because of `--local`).
662 git -C alternate repack -ad &&
667 object="$(git -C ../alternate rev-parse HEAD:base.t)" &&
668 git -C ../alternate cat-file -p $object >contents &&
670 # Write some reachable objects and two unreachable ones: one
671 # that the alternate has and another that is unique.
673 git hash-object -w -t blob contents &&
674 cruft="$(echo cruft | git hash-object -w -t blob --stdin)" &&
676 ( cd ../alternate/.git/objects && pwd ) \
677 >.git/objects/info/alternates &&
679 test_path_is_file $objdir/$(test_oid_to_path $cruft) &&
680 test_path_is_file $objdir/$(test_oid_to_path $object) &&
682 git repack -d --cruft --local &&
684 test-tool pack-mtimes "$(basename $(ls $packdir/pack-*.mtimes))" \
686 ! grep $object objects &&
691 test_expect_success
'MIDX bitmaps tolerate reachable cruft objects' '
693 test_when_finished "rm -fr repo" &&
697 test_commit reachable &&
699 unreachable="$(git rev-parse cruft)" &&
701 git reset --hard $unreachable^ &&
703 git reflog expire --all --expire=all &&
705 git repack --cruft -d &&
707 # resurrect the unreachable object via a new commit. the
708 # new commit will get selected for a bitmap, but be
709 # missing one of its parents from the selected packs.
710 git reset --hard $unreachable &&
711 test_commit resurrect &&
713 git repack --write-midx --write-bitmap-index --geometric=2 -d
717 test_expect_success
'cruft objects are freshend via loose' '
719 test_when_finished "rm -fr repo" &&
723 echo "cruft" >contents &&
724 blob="$(git hash-object -w -t blob contents)" &&
725 loose="$objdir/$(test_oid_to_path $blob)" &&
729 git repack --cruft -d &&
731 test_path_is_missing "$loose" &&
732 test-tool pack-mtimes "$(basename "$(ls $packdir/pack-*.mtimes)")" >cruft &&
733 grep "$blob" cruft &&
735 # write the same object again
736 git hash-object -w -t blob contents &&
738 test_path_is_file "$loose"