run-command: fix detaching when running auto maintenance

In the past, we used to execute `git gc --auto` as part of our automatic
housekeeping routines. As git-gc(1) may require quite some time to
perform the housekeeping, it knows to detach itself and run in the
background so that the user can continue their work.

Eventually, we refactored our automatic housekeeping to instead use the
more flexible git-maintenance(1) command. The upside of this new infra
is that the user can configure which maintenance tasks are performed, at
least to a certain degree. So while it continues to run git-gc(1) by
default, it can also be adapted to e.g. use git-multi-pack-index(1) for
maintenance of the object database.

The auto-detach of the new infra is somewhat broken though once the user
configures non-standard tasks. The problem is essentially that we detach
at the wrong level in the process hierarchy: git-maintenance(1) never
detaches itself, but instead it continues to be git-gc(1) which does.

When configured to only run the git-gc(1) maintenance task, then the
result is basically the same as before. But when configured to run other
tasks, then git-maintenance(1) will wait for these to run to completion.
Even worse, it may be that git-gc(1) runs concurrently with other
housekeeping tasks, stomping on each others feet.

Fix this bug by asking git-gc(1) to not detach when it is being invoked
via git-maintenance(1). Instead, git-maintenance(1) now respects a new
config "maintenance.autoDetach", the equivalent of "gc.autoDetach", and
detaches itself into the background when running as part of our auto
maintenance. This should continue to behave the same for all users which
use the git-gc(1) task, only. For others though, it means that we now
properly perform all tasks in the background. The default behaviour of
git-maintenance(1) when executed by the user does not change, it will
remain in the foreground unless they pass the `--detach` option.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Patrick Steinhardt 2024-08-16 12:45:17 +02:00 committed by Junio C Hamano
parent a6affd3343
commit 98077d06b2
6 changed files with 62 additions and 14 deletions

View File

@ -40,7 +40,8 @@ use, it'll affect how the auto pack limit works.
gc.autoDetach::
Make `git gc --auto` return immediately and run in the background
if the system supports it. Default is true.
if the system supports it. Default is true. This config variable acts
as a fallback in case `maintenance.autoDetach` is not set.
gc.bigPackThreshold::
If non-zero, all non-cruft packs larger than this limit are kept

View File

@ -3,6 +3,17 @@ maintenance.auto::
`git maintenance run --auto` after doing their normal work. Defaults
to true.
maintenance.autoDetach::
Many Git commands trigger automatic maintenance after they have
written data into the repository. This boolean config option
controls whether this automatic maintenance shall happen in the
foreground or whether the maintenance process shall detach and
continue to run in the background.
+
If unset, the value of `gc.autoDetach` is used as a fallback. Defaults
to true if both are unset, meaning that the maintenance process will
detach.
maintenance.strategy::
This string config option provides a way to specify one of a few
recommended schedules for background maintenance. This only affects

View File

@ -1063,6 +1063,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts,
strvec_push(&child.args, "--quiet");
else
strvec_push(&child.args, "--no-quiet");
strvec_push(&child.args, "--no-detach");
return run_command(&child);
}

View File

@ -1808,16 +1808,26 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
int prepare_auto_maintenance(int quiet, struct child_process *maint)
{
int enabled;
int enabled, auto_detach;
if (!git_config_get_bool("maintenance.auto", &enabled) &&
!enabled)
return 0;
/*
* When `maintenance.autoDetach` isn't set, then we fall back to
* honoring `gc.autoDetach`. This is somewhat weird, but required to
* retain behaviour from when we used to run git-gc(1) here.
*/
if (git_config_get_bool("maintenance.autodetach", &auto_detach) &&
git_config_get_bool("gc.autodetach", &auto_detach))
auto_detach = 1;
maint->git_cmd = 1;
maint->close_object_store = 1;
strvec_pushl(&maint->args, "maintenance", "run", "--auto", NULL);
strvec_push(&maint->args, quiet ? "--quiet" : "--no-quiet");
strvec_push(&maint->args, auto_detach ? "--detach" : "--no-detach");
return 1;
}

View File

@ -229,7 +229,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
GIT_TRACE2_EVENT="$PWD/trace1.event" \
git -C pc1 fetch --refetch origin &&
test_subcommand git maintenance run --auto --no-quiet <trace1.event &&
test_subcommand git maintenance run --auto --no-quiet --detach <trace1.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event &&
@ -238,7 +238,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
-c gc.autoPackLimit=0 \
-c maintenance.incremental-repack.auto=1234 \
-C pc1 fetch --refetch origin &&
test_subcommand git maintenance run --auto --no-quiet <trace2.event &&
test_subcommand git maintenance run --auto --no-quiet --detach <trace2.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event &&
@ -247,7 +247,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
-c gc.autoPackLimit=1234 \
-c maintenance.incremental-repack.auto=0 \
-C pc1 fetch --refetch origin &&
test_subcommand git maintenance run --auto --no-quiet <trace3.event &&
test_subcommand git maintenance run --auto --no-quiet --detach <trace3.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event
'

View File

@ -49,22 +49,47 @@ test_expect_success 'run [--auto|--quiet]' '
git maintenance run --auto 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
git maintenance run --no-quiet 2>/dev/null &&
test_subcommand git gc --quiet <run-no-auto.txt &&
test_subcommand ! git gc --auto --quiet <run-auto.txt &&
test_subcommand git gc --no-quiet <run-no-quiet.txt
test_subcommand git gc --quiet --no-detach <run-no-auto.txt &&
test_subcommand ! git gc --auto --quiet --no-detach <run-auto.txt &&
test_subcommand git gc --no-quiet --no-detach <run-no-quiet.txt
'
test_expect_success 'maintenance.auto config option' '
GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet <default &&
test_subcommand git maintenance run --auto --quiet --detach <default &&
GIT_TRACE2_EVENT="$(pwd)/true" \
git -c maintenance.auto=true \
commit --quiet --allow-empty -m 2 &&
test_subcommand git maintenance run --auto --quiet <true &&
test_subcommand git maintenance run --auto --quiet --detach <true &&
GIT_TRACE2_EVENT="$(pwd)/false" \
git -c maintenance.auto=false \
commit --quiet --allow-empty -m 3 &&
test_subcommand ! git maintenance run --auto --quiet <false
test_subcommand ! git maintenance run --auto --quiet --detach <false
'
for cfg in maintenance.autoDetach gc.autoDetach
do
test_expect_success "$cfg=true config option" '
test_when_finished "rm -f trace" &&
test_config $cfg true &&
GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet --detach <trace
'
test_expect_success "$cfg=false config option" '
test_when_finished "rm -f trace" &&
test_config $cfg false &&
GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet --no-detach <trace
'
done
test_expect_success "maintenance.autoDetach overrides gc.autoDetach" '
test_when_finished "rm -f trace" &&
test_config maintenance.autoDetach false &&
test_config gc.autoDetach true &&
GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet --no-detach <trace
'
test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' '
@ -129,9 +154,9 @@ test_expect_success 'run --task=<task>' '
git maintenance run --task=commit-graph 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
test_subcommand ! git gc --quiet <run-commit-graph.txt &&
test_subcommand git gc --quiet <run-gc.txt &&
test_subcommand git gc --quiet <run-both.txt &&
test_subcommand ! git gc --quiet --no-detach <run-commit-graph.txt &&
test_subcommand git gc --quiet --no-detach <run-gc.txt &&
test_subcommand git gc --quiet --no-detach <run-both.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt