builtin/gc: add a `--detach` flag

When running `git gc --auto`, the command will by default detach and continue running in the background. This behaviour can be tweaked via the `gc.autoDetach` config, but not via a command line switch. We need that in a subsequent commit though, where git-maintenance(1) will want to ask its git-gc(1) child process to not detach anymore. Add a `--[no-]detach` flag that does this for us. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Aug 16, 2024 at 12:45 UTC c7185df01bb68091d3bdea2fe192f4b34e169c9d
3 files changed +84 -36
Documentation/git-gc.txt
+4 -1
@@ -9,7 +9,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
9 SYNOPSIS
10 --------
11 [verse]
12 -'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] [--force] [--keep-largest-pack]
12 +'git gc' [--aggressive] [--auto] [--[no-]detach] [--quiet] [--prune=<date> | --no-prune] [--force] [--keep-largest-pack]
13
14 DESCRIPTION
15 -----------
@@ -53,6 +53,9 @@ configuration options such as `gc.auto` and `gc.autoPackLimit`, all
53 other housekeeping tasks (e.g. rerere, working trees, reflog...) will
54 be performed as well.
55
56 +--[no-]detach::
57 + Run in the background if the system supports it. This option overrides
58 + the `gc.autoDetach` config.
59
60 --[no-]cruft::
61 When expiring unreachable objects, pack them separately into a
builtin/gc.c
+39 -31
@@ -242,9 +242,13 @@ static enum schedule_priority parse_schedule(const char *value)
242
243 struct maintenance_run_opts {
244 int auto_flag;
245 + int detach;
246 int quiet;
247 enum schedule_priority schedule;
248 };
249 +#define MAINTENANCE_RUN_OPTS_INIT { \
250 + .detach = -1, \
251 +}
252
253 static int pack_refs_condition(UNUSED struct gc_config *cfg)
254 {
@@ -664,7 +668,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
668 int keep_largest_pack = -1;
669 timestamp_t dummy;
670 struct child_process rerere_cmd = CHILD_PROCESS_INIT;
667 - struct maintenance_run_opts opts = {0};
671 + struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
672 struct gc_config cfg = GC_CONFIG_INIT;
673 const char *prune_expire_sentinel = "sentinel";
674 const char *prune_expire_arg = prune_expire_sentinel;
@@ -681,6 +685,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
685 OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
686 OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"),
687 PARSE_OPT_NOCOMPLETE),
688 + OPT_BOOL(0, "detach", &opts.detach,
689 + N_("perform garbage collection in the background")),
690 OPT_BOOL_F(0, "force", &force,
691 N_("force running gc even if there may be another gc running"),
692 PARSE_OPT_NOCOMPLETE),
@@ -729,6 +735,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
735 strvec_push(&repack, "-q");
736
737 if (opts.auto_flag) {
738 + if (cfg.detach_auto && opts.detach < 0)
739 + opts.detach = 1;
740 +
741 /*
742 * Auto-gc should be least intrusive as possible.
743 */
@@ -738,38 +747,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
747 }
748
749 if (!quiet) {
741 - if (cfg.detach_auto)
750 + if (opts.detach > 0)
751 fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
752 else
753 fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
754 fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
755 }
747 - if (cfg.detach_auto) {
748 - ret = report_last_gc_error();
749 - if (ret == 1) {
750 - /* Last gc --auto failed. Skip this one. */
751 - ret = 0;
752 - goto out;
753 -
754 - } else if (ret) {
755 - /* an I/O error occurred, already reported */
756 - goto out;
757 - }
758 -
759 - if (lock_repo_for_gc(force, &pid)) {
760 - ret = 0;
761 - goto out;
762 - }
763 -
764 - gc_before_repack(&opts, &cfg); /* dies on failure */
765 - delete_tempfile(&pidfile);
766 -
767 - /*
768 - * failure to daemonize is ok, we'll continue
769 - * in foreground
770 - */
771 - daemonized = !daemonize();
772 - }
756 } else {
757 struct string_list keep_pack = STRING_LIST_INIT_NODUP;
758
@@ -784,6 +767,33 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
767 string_list_clear(&keep_pack, 0);
768 }
769
770 + if (opts.detach > 0) {
771 + ret = report_last_gc_error();
772 + if (ret == 1) {
773 + /* Last gc --auto failed. Skip this one. */
774 + ret = 0;
775 + goto out;
776 +
777 + } else if (ret) {
778 + /* an I/O error occurred, already reported */
779 + goto out;
780 + }
781 +
782 + if (lock_repo_for_gc(force, &pid)) {
783 + ret = 0;
784 + goto out;
785 + }
786 +
787 + gc_before_repack(&opts, &cfg); /* dies on failure */
788 + delete_tempfile(&pidfile);
789 +
790 + /*
791 + * failure to daemonize is ok, we'll continue
792 + * in foreground
793 + */
794 + daemonized = !daemonize();
795 + }
796 +
797 name = lock_repo_for_gc(force, &pid);
798 if (name) {
799 if (opts.auto_flag) {
@@ -1537,7 +1547,7 @@ static int task_option_parse(const struct option *opt UNUSED,
1547 static int maintenance_run(int argc, const char **argv, const char *prefix)
1548 {
1549 int i;
1540 - struct maintenance_run_opts opts;
1550 + struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
1551 struct gc_config cfg = GC_CONFIG_INIT;
1552 struct option builtin_maintenance_run_options[] = {
1553 OPT_BOOL(0, "auto", &opts.auto_flag,
@@ -1554,8 +1564,6 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)
1564 };
1565 int ret;
1566
1557 - memset(&opts, 0, sizeof(opts));
1558 -
1567 opts.quiet = !isatty(2);
1568
1569 for (i = 0; i < TASK__COUNT; i++)
t/t6500-gc.sh
+41 -4
@@ -338,14 +338,14 @@ test_expect_success 'gc.maxCruftSize sets appropriate repack options' '
338 test_subcommand $cruft_max_size_opts --max-cruft-size=3145728 <trace2.txt
339 '
340
341 -run_and_wait_for_auto_gc () {
341 +run_and_wait_for_gc () {
342 # We read stdout from gc for the side effect of waiting until the
343 # background gc process exits, closing its fd 9. Furthermore, the
344 # variable assignment from a command substitution preserves the
345 # exit status of the main gc process.
346 # Note: this fd trickery doesn't work on Windows, but there is no
347 # need to, because on Win the auto gc always runs in the foreground.
348 - doesnt_matter=$(git gc --auto 9>&1)
348 + doesnt_matter=$(git gc "$@" 9>&1)
349 }
350
351 test_expect_success 'background auto gc does not run if gc.log is present and recent but does if it is old' '
@@ -361,7 +361,7 @@ test_expect_success 'background auto gc does not run if gc.log is present and re
361 test-tool chmtime =-345600 .git/gc.log &&
362 git gc --auto &&
363 test_config gc.logexpiry 2.days &&
364 - run_and_wait_for_auto_gc &&
364 + run_and_wait_for_gc --auto &&
365 ls .git/objects/pack/pack-*.pack >packs &&
366 test_line_count = 1 packs
367 '
@@ -391,11 +391,48 @@ test_expect_success 'background auto gc respects lock for all operations' '
391 printf "%d %s" "$shell_pid" "$hostname" >.git/gc.pid &&
392
393 # our gc should exit zero without doing anything
394 - run_and_wait_for_auto_gc &&
394 + run_and_wait_for_gc --auto &&
395 (ls -1 .git/refs/heads .git/reftable >actual || true) &&
396 test_cmp expect actual
397 '
398
399 +test_expect_success '--detach overrides gc.autoDetach=false' '
400 + test_when_finished "rm -rf repo" &&
401 + git init repo &&
402 + (
403 + cd repo &&
404 +
405 + # Prepare the repository such that git-gc(1) ends up repacking.
406 + test_commit "$(test_oid blob17_1)" &&
407 + test_commit "$(test_oid blob17_2)" &&
408 + git config gc.autodetach false &&
409 + git config gc.auto 2 &&
410 +
411 + # Note that we cannot use `test_cmp` here to compare stderr
412 + # because it may contain output from `set -x`.
413 + run_and_wait_for_gc --auto --detach 2>actual &&
414 + test_grep "Auto packing the repository in background for optimum performance." actual
415 + )
416 +'
417 +
418 +test_expect_success '--no-detach overrides gc.autoDetach=true' '
419 + test_when_finished "rm -rf repo" &&
420 + git init repo &&
421 + (
422 + cd repo &&
423 +
424 + # Prepare the repository such that git-gc(1) ends up repacking.
425 + test_commit "$(test_oid blob17_1)" &&
426 + test_commit "$(test_oid blob17_2)" &&
427 + git config gc.autodetach true &&
428 + git config gc.auto 2 &&
429 +
430 + GIT_PROGRESS_DELAY=0 git gc --auto --no-detach 2>output &&
431 + test_grep "Auto packing the repository for optimum performance." output &&
432 + test_grep "Collecting referenced commits: 2, done." output
433 + )
434 +'
435 +
436 # DO NOT leave a detached auto gc process running near the end of the
437 # test script: it can run long enough in the background to racily
438 # interfere with the cleanup in 'test_done'.