Commit 88cfc54552 ("nix-prefetch-git: disable maintenance mode via
environment variables") disabled maintenance.auto (git's new
auto-maintenance) via GIT_CONFIG_*, which propagates to submodules. But
the legacy auto-gc path (gc.auto plus detached gc.autoDetach) is still
enabled, and it keeps repacking .git/modules/<sub>/objects after a nested
submodule reports "checked out", racing the later `rm -rf .git`:
- rm empties objects/pack, the detached gc drops a fresh pack in, and
rmdir fails with "Directory not empty" (exit 123); or
- removal partially completes, leaving varying .git remnants, yielding a
non-deterministic NAR hash.
Disable gc.auto/gc.autoDetach alongside maintenance.auto, using the same
GIT_CONFIG_* mechanism so the settings reach every git invocation and its
children (including nested submodule fetches). Fold the repeated
boilerplate into a small git_config_env helper.
The successful output is unchanged (.git is removed either way), so
pinned fetchgit hashes stay valid; this only makes removal deterministic.
Fixes#524215.
Assisted-by: Claude Code (claude-opus-4-8)
The commit 011471c7f2 ("fetchgit: disable git maintenance") disabled
git's maintenance mode, but that does not affect git repositories with
submodules.
Switch to using the GIT_CONFIG_* environment variables. This is done in
a way to make the approach more generic, but also kinda not the best
("insane") so that it works with any future change, even with
`impureEnvVars`.
Maybe `git -c maintenance.auto=false` can work but I cannot figure out
how to use that here. I would prefer disabling maintenance mode in Git's
derivation itself but did not find any build-time knob to toggle that.
By default, Git commands will trigger background processes to perform
repository maintenance. As of Git v2.54.0, and in particular as of
452b12c2e0 (builtin/maintenance: use "geometric" strategy by default,
2026-02-24), Git has changed the strategy it uses to decide whether
maintenance needs performing, and as a result will kick off background
maintenance processes much more often.
These background processes will potentially change the contents of the
`.git` directory while `nix-prefetch-git` is deleting it. This results in
effectively random failures, when files are added to the `.git`
directory during the `rm -rf` operation (causing it to fail) or after
the operation has completed (causing the `.git` directory to be
recreated and resulting in a FOD hash mismatch).
This also causes problems for the determinism of Git's own tests.
They've resolved this in 09505b1115 (t: fix races caused by background
maintenance, 2026-02-24) by having the maintenance processes run in the
foreground. The discussion in that commit explains they considered just
disabling maintenance entirely, but opted for the foreground option
because disabling maintenance would mean the tests looked even less like
how most Git users work with Git.
`nix-prefetch-git` is nothing like most Git users. In almost every
case, the `.git` directory will be deleted during or immediately after
the script is called. The repositories certainly don't benefit from
ongoing maintenance. To avoid the non-determinism created by the
background maintenance processes, just disable maintenance entirely.
This is done with `git config` after initialising the repository, rather
than `git config --global` earlier in the script, as `git config
--global` will fail if `fetchgit` was called with a `gitConfig`
argument.
It would be possible to set this option with
`GIT_CONFIG_[COUNT|KEY_<n>|VALUE_<n>]` environment variables; I've opted
against that because (a) they're less familiar to most people who might
want to come to edit this script in future and (b) because if someone is
using those values themselves via `impureEnvVars` or similar, making our
configuration play nicely with someone else's would require some fairly
complex logic.
I haven't made this a configurable option: any use of `nix-prefetch-git`
will run with maintenance disabled, as I cannot think of a use case
where it would be useful. If there is such a use case, it would be
straightforward to extend `nix-prefetch-git` and `fetchgit` to allow
users to change the behaviour.
Fixes#524215.
When leaveDotGit == true,
restore the cloning behaviour before
commit 7e085677f9 ("nix-prefetch-git: dont't fetch tags when deep clone unless leaving .git")
to preserve the fragile hashes of .git sources.
Clean up fetchTagsCompat,
as fetchTagsCompat is no longer needed
after restoring the old tag-fetching flags.
Passing a `gitConfigFile` argument as a string allows setting a custom
git config (as `$GIT_CONFIG_GLOBAL`). The most obvious use is to
override URLs with custom mirrors, but others are possible too.
Adds a new argument to `nix-prefetch-git`: `--no-add-path`. It disables
adding the path to the store. It is useful when working with a read-only
store.