midx.c: prevent `expire` from removing the cruft pack

The `expire` sub-command unlinks any packs that are (a) contained in the MIDX, but (b) have no objects referenced by the MIDX. This sub-command ignores `.keep` packs, which remain on-disk even if they have no objects referenced by the MIDX. Cruft packs, however, aren't given the same treatment: if none of the objects contained in the cruft pack are selected from the cruft pack by the MIDX, then the cruft pack is eligible to be expired. This is less than desireable, since the cruft pack has important metadata about the individual object mtimes, which is useful to determine how quickly an object should age out of the repository when pruning. Ordinarily, we wouldn't expect the contents of a cruft pack to duplicated across non-cruft packs (and we'd expect to see the MIDX select all cruft objects from other sources even less often). But nonetheless, it is still possible to trick the `expire` sub-command into removing the `.mtimes` file in this circumstance. Teach the `expire` sub-command to ignore cruft packs in the same manner as it does `.keep` packs, in order to keep their metadata around, even when they are unreferenced by the MIDX. Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Sep 19, 2022 at 21:55 UTC 757d457907e3efa8eb911b772a690661cd432da5
3 files changed +33 -3
Documentation/git-multi-pack-index.txt
+2 -2
@@ -72,8 +72,8 @@ verify::
72 expire::
73 Delete the pack-files that are tracked by the MIDX file, but
74 have no objects referenced by the MIDX (with the exception of
75 - `.keep` packs). Rewrite the MIDX file afterward to remove all
76 - references to these pack-files.
75 + `.keep` packs and cruft packs). Rewrite the MIDX file afterward
76 + to remove all references to these pack-files.
77
78 repack::
79 Create a new pack-file containing objects in small pack-files
midx.c
+1 -1
@@ -1839,7 +1839,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
1839 if (prepare_midx_pack(r, m, i))
1840 continue;
1841
1842 - if (m->packs[i]->pack_keep)
1842 + if (m->packs[i]->pack_keep || m->packs[i]->is_cruft)
1843 continue;
1844
1845 pack_name = xstrdup(m->packs[i]->pack_name);
t/t5319-multi-pack-index.sh
+30
@@ -847,6 +847,36 @@ test_expect_success 'expire respects .keep files' '
847 )
848 '
849
850 +test_expect_success 'expiring unreferenced cruft pack retains pack' '
851 + git init repo &&
852 + test_when_finished "rm -fr repo" &&
853 + (
854 + cd repo &&
855 +
856 + test_commit base &&
857 + test_commit --no-tag unreachable &&
858 + unreachable=$(git rev-parse HEAD) &&
859 +
860 + git reset --hard base &&
861 + git reflog expire --all --expire=all &&
862 + git repack --cruft -d &&
863 + mtimes="$(ls $objdir/pack/pack-*.mtimes)" &&
864 +
865 + echo "base..$unreachable" >in &&
866 + pack="$(git pack-objects --revs --delta-base-offset \
867 + $objdir/pack/pack <in)" &&
868 +
869 + # Preferring the contents of "$pack" will leave the
870 + # cruft pack unreferenced (ie., none of the objects
871 + # contained in the cruft pack will have their MIDX copy
872 + # selected from the cruft pack).
873 + git multi-pack-index write --preferred-pack="pack-$pack.pack" &&
874 + git multi-pack-index expire &&
875 +
876 + test_path_is_file "$mtimes"
877 + )
878 +'
879 +
880 test_expect_success 'repack --batch-size=0 repacks everything' '
881 cp -r dup dup2 &&
882 (