t: fix races caused by background maintenance

Many Git commands spawn git-maintenance(1) to optimize the repository in the background. By default, performing the maintenance is for most of the part asynchronous: we fork the executable and then continue with the rest of our business logic. This is working as expected for our users, but this behaviour is somewhat problematic for our test suite as this is inherently racy. We have many tests that verify the on-disk state of repositories, and those tests may easily race with our background maintenance. In a similar fashion, we may end up with processes that "leak" out of a current test case. Until now this tends to not be much of a problem. Our maintenance uses git-gc(1) by default, which knows to bail out in case there aren't either too many packfiles or too many loose objects. So even if other data structures would need to be optimized, we won't do so unless the object database also needs optimizations. This is about to change though, as a subsequent commit will switch to the "geometric" maintenance strategy as a default. The consequence is that we will run required optimizations even if the object database is well-optimized. And this uncovers races between our test suite and background maintenance all over the place. Disabling maintenance outright in our test suite is not really an option, as it would result in significant divergence from the "real world" and reduce our test coverage. But we've got an alternative up our sleeves: we can ensure that garbage collection runs synchronously by overriding the "maintenance.autoDetach" configuration. Of course that also diverges from the real world, as we now stop testing that background maintenance interacts in a benign way with normal Git commands. But on the other hand this ensures that the maintenance itself does not for example lead to data loss in a more reproducible way. Another concern is that this would make execution of the test suite much slower. But a quick benchmark on my machine demonstrates that this does not seem to be the case: Benchmark 1: meson test (revision = HEAD~) Time (mean ± σ): 131.182 s ± 1.293 s [User: 853.737 s, System: 1160.479 s] Range (min … max): 130.001 s … 132.563 s 3 runs Benchmark 2: meson test (revision = HEAD) Time (mean ± σ): 129.554 s ± 0.507 s [User: 849.040 s, System: 1152.664 s] Range (min … max): 129.000 s … 129.994 s 3 runs Summary meson test (revision = HEAD) ran 1.01 ± 0.01 times faster than meson test (revision = HEAD~) Funny enough, it even seems as if this speeds up test execution ever so slightly, but that may just as well be noise. Introduce a new `GIT_TEST_MAINT_AUTO_DETACH` environment variable that allows us to override the auto-detach behaviour and set that variable in our tests. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Feb 24, 2026 at 09:45 UTC 09505b11153a20e1c6c572d41db778171dd19cbc
4 files changed +11 -4
run-command.c
+1 -1
@@ -1828,7 +1828,7 @@ int prepare_auto_maintenance(int quiet, struct child_process *maint)
1828 */
1829 if (repo_config_get_bool(the_repository, "maintenance.autodetach", &auto_detach) &&
1830 repo_config_get_bool(the_repository, "gc.autodetach", &auto_detach))
1831 - auto_detach = 1;
1831 + auto_detach = git_env_bool("GIT_TEST_MAINT_AUTO_DETACH", true);
1832
1833 maint->git_cmd = 1;
1834 maint->close_object_store = 1;
t/t5616-partial-clone.sh
+3 -3
@@ -229,7 +229,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
229
230 GIT_TRACE2_EVENT="$PWD/trace1.event" \
231 git -C pc1 fetch --refetch origin &&
232 - test_subcommand git maintenance run --auto --no-quiet --detach <trace1.event &&
232 + test_subcommand git maintenance run --auto --no-quiet --no-detach <trace1.event &&
233 grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event &&
234 grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event &&
235
@@ -238,7 +238,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
238 -c gc.autoPackLimit=0 \
239 -c maintenance.incremental-repack.auto=1234 \
240 -C pc1 fetch --refetch origin &&
241 - test_subcommand git maintenance run --auto --no-quiet --detach <trace2.event &&
241 + test_subcommand git maintenance run --auto --no-quiet --no-detach <trace2.event &&
242 grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event &&
243 grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event &&
244
@@ -247,7 +247,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
247 -c gc.autoPackLimit=1234 \
248 -c maintenance.incremental-repack.auto=0 \
249 -C pc1 fetch --refetch origin &&
250 - test_subcommand git maintenance run --auto --no-quiet --detach <trace3.event &&
250 + test_subcommand git maintenance run --auto --no-quiet --no-detach <trace3.event &&
251 grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event &&
252 grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event
253 '
t/t7900-maintenance.sh
+3
@@ -7,6 +7,9 @@ test_description='git maintenance builtin'
7 GIT_TEST_COMMIT_GRAPH=0
8 GIT_TEST_MULTI_PACK_INDEX=0
9
10 +# Ensure that auto-maintenance detaches as usual.
11 +sane_unset GIT_TEST_MAINT_AUTO_DETACH
12 +
13 test_lazy_prereq XMLLINT '
14 xmllint --version
15 '
t/test-lib.sh
+4
@@ -1947,6 +1947,10 @@ test_lazy_prereq COMPAT_HASH '
1947 GIT_TEST_MAINT_SCHEDULER="none:exit 1"
1948 export GIT_TEST_MAINT_SCHEDULER
1949
1950 +# Ensure that tests cannot race with background maintenance by default.
1951 +GIT_TEST_MAINT_AUTO_DETACH="false"
1952 +export GIT_TEST_MAINT_AUTO_DETACH
1953 +
1954 # Does this platform support `git fsmonitor--daemon`
1955 #
1956 test_lazy_prereq FSMONITOR_DAEMON '