builtin/gc.c: conditionally avoid pruning objects via loose

Expose the new `git repack --cruft` mode from `git gc` via a new opt-in flag. When invoked like `git gc --cruft`, `git gc` will avoid exploding unreachable objects as loose ones, and instead create a cruft pack and `.mtimes` file. Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed May 20, 2022 at 19:18 UTC 5b92477f896f147d02bd2e9168a780940b57cfc5
4 files changed +65 -8
Documentation/config/gc.txt
+14 -7
@@ -81,14 +81,21 @@ gc.packRefs::
81 to enable it within all non-bare repos or it can be set to a
82 boolean value. The default is `true`.
83
84 +gc.cruftPacks::
85 + Store unreachable objects in a cruft pack (see
86 + linkgit:git-repack[1]) instead of as loose objects. The default
87 + is `false`.
88 +
89 gc.pruneExpire::
85 - When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'.
86 - Override the grace period with this config variable. The value
87 - "now" may be used to disable this grace period and always prune
88 - unreachable objects immediately, or "never" may be used to
89 - suppress pruning. This feature helps prevent corruption when
90 - 'git gc' runs concurrently with another process writing to the
91 - repository; see the "NOTES" section of linkgit:git-gc[1].
90 + When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'
91 + (and 'repack --cruft --cruft-expiration 2.weeks.ago' if using
92 + cruft packs via `gc.cruftPacks` or `--cruft`). Override the
93 + grace period with this config variable. The value "now" may be
94 + used to disable this grace period and always prune unreachable
95 + objects immediately, or "never" may be used to suppress pruning.
96 + This feature helps prevent corruption when 'git gc' runs
97 + concurrently with another process writing to the repository; see
98 + the "NOTES" section of linkgit:git-gc[1].
99
100 gc.worktreePruneExpire::
101 When 'git gc' is run, it calls
Documentation/git-gc.txt
+5
@@ -54,6 +54,11 @@ other housekeeping tasks (e.g. rerere, working trees, reflog...) will
54 be performed as well.
55
56
57 +--cruft::
58 + When expiring unreachable objects, pack them separately into a
59 + cruft pack instead of storing the loose objects as loose
60 + objects.
61 +
62 --prune=<date>::
63 Prune loose objects older than date (default is 2 weeks ago,
64 overridable by the config variable `gc.pruneExpire`).
builtin/gc.c
+9 -1
@@ -42,6 +42,7 @@ static const char * const builtin_gc_usage[] = {
42
43 static int pack_refs = 1;
44 static int prune_reflogs = 1;
45 +static int cruft_packs = 0;
46 static int aggressive_depth = 50;
47 static int aggressive_window = 250;
48 static int gc_auto_threshold = 6700;
@@ -152,6 +153,7 @@ static void gc_config(void)
153 git_config_get_int("gc.auto", &gc_auto_threshold);
154 git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
155 git_config_get_bool("gc.autodetach", &detach_auto);
156 + git_config_get_bool("gc.cruftpacks", &cruft_packs);
157 git_config_get_expiry("gc.pruneexpire", &prune_expire);
158 git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
159 git_config_get_expiry("gc.logexpiry", &gc_log_expire);
@@ -331,7 +333,11 @@ static void add_repack_all_option(struct string_list *keep_pack)
333 {
334 if (prune_expire && !strcmp(prune_expire, "now"))
335 strvec_push(&repack, "-a");
334 - else {
336 + else if (cruft_packs) {
337 + strvec_push(&repack, "--cruft");
338 + if (prune_expire)
339 + strvec_pushf(&repack, "--cruft-expiration=%s", prune_expire);
340 + } else {
341 strvec_push(&repack, "-A");
342 if (prune_expire)
343 strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
@@ -551,6 +557,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
557 { OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
558 N_("prune unreferenced objects"),
559 PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
560 + OPT_BOOL(0, "cruft", &cruft_packs, N_("pack unreferenced objects separately")),
561 OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
562 OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"),
563 PARSE_OPT_NOCOMPLETE),
@@ -670,6 +677,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
677 die(FAILED_RUN, repack.v[0]);
678
679 if (prune_expire) {
680 + /* run `git prune` even if using cruft packs */
681 strvec_push(&prune, prune_expire);
682 if (quiet)
683 strvec_push(&prune, "--no-progress");
t/t5329-pack-objects-cruft.sh
+37
@@ -429,6 +429,43 @@ test_expect_success 'loose objects mtimes upsert others' '
429 )
430 '
431
432 +test_expect_success 'expiring cruft objects with git gc' '
433 + git init repo &&
434 + test_when_finished "rm -fr repo" &&
435 + (
436 + cd repo &&
437 +
438 + test_commit reachable &&
439 + git branch -M main &&
440 + git checkout --orphan other &&
441 + test_commit unreachable &&
442 +
443 + git checkout main &&
444 + git branch -D other &&
445 + git tag -d unreachable &&
446 + # objects are not cruft if they are contained in the reflogs
447 + git reflog expire --all --expire=all &&
448 +
449 + git rev-list --objects --all --no-object-names >reachable.raw &&
450 + git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
451 + sort <reachable.raw >reachable &&
452 + comm -13 reachable objects >unreachable &&
453 +
454 + git repack --cruft -d &&
455 +
456 + mtimes=$(ls .git/objects/pack/pack-*.mtimes) &&
457 + test_path_is_file $mtimes &&
458 +
459 + git gc --cruft --prune=now &&
460 +
461 + git cat-file --batch-all-objects --batch-check="%(objectname)" >objects &&
462 +
463 + comm -23 unreachable objects >removed &&
464 + test_cmp unreachable removed &&
465 + test_path_is_missing $mtimes
466 + )
467 +'
468 +
469 test_expect_success 'cruft packs are not included in geometric repack' '
470 git init repo &&
471 test_when_finished "rm -fr repo" &&