aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-12 02:33:43 +0000
committertv <tv@krebsco.de>2026-11-12 02:33:43 +0000
commit1dd511bd6920b428dbd48f36ac646da8463fcbdd (patch)
tree2e36ef4d6e7dc31850ddd8b7384058090145ea38
parent5906453bc0c6e31cee65a20aa8e7811b6eaf3b1e (diff)
populate: replace symlinks before syncing with rsync
rsync follows a symlink at the target path, so a source switching from the flake or flake-input type to file, pass, passage, or pipe got written into the store path the link points to, and one switching from the symlink type into what that link points to, e.g. another source. Populate removes a symlink at the target path before syncing with rsync. The roots in .populate/flake-inputs/<name> of a source that is neither a flake nor a flake-input source anymore get removed as well. An edge case switches a flake source to a file source.
-rw-r--r--pkgs/populate/default.nix18
-rw-r--r--tests/scenarios.nix19
2 files changed, 37 insertions, 0 deletions
diff --git a/pkgs/populate/default.nix b/pkgs/populate/default.nix
index 708e7a3..66c88c1 100644
--- a/pkgs/populate/default.nix
+++ b/pkgs/populate/default.nix
@@ -158,6 +158,17 @@ let
mkdir -p ${quote flakesDir}
${concatMapStrings flake sorted.result}
''}
+ # Remove roots of flake inputs of sources that are neither flake nor
+ # flake-input sources anymore.
+ ${concatMapStrings (name: /* sh */ ''
+ rm -fR ${quote "${target.path}/.populate/flake-inputs/${name}"}
+ '') (attrNames (filterAttrs
+ (_: source: !elem source.type [ "flake" "flake-input" ])
+ sources))}
+ if test -d ${quote "${target.path}/.populate/flake-inputs"} &&
+ test -z "$(ls -A ${quote "${target.path}/.populate/flake-inputs"})"; then
+ rmdir ${quote "${target.path}/.populate/flake-inputs"}
+ fi
# Remove flakes of sources that no longer draw inputs from other sources.
if test -d ${quote flakesDir}; then
ls -A ${quote flakesDir} | while read -r name; do
@@ -767,6 +778,13 @@ let
'';
rsync' = target: config: sourcePath: /* sh */ ''
+ # rsync writes through a symlink at the target path, e.g. one a symlink or
+ # flake source left there, into what it points to.
+ ${runShell target /* sh */ ''
+ if test -L ${quote target.path}; then
+ rm ${quote target.path}
+ fi
+ ''}
source_path=${sourcePath}
if test -d "$source_path"; then
source_path=$source_path/
diff --git a/tests/scenarios.nix b/tests/scenarios.nix
index a738eca..38f8736 100644
--- a/tests/scenarios.nix
+++ b/tests/scenarios.nix
@@ -808,6 +808,25 @@ assert !(krops.isLocalTarget (targets.remote "/"));
check = checkFlake { attr = "msg"; expected = "rel:lib"; };
};
})
+ (s "flake-to-file" {
+ phases = [
+ {
+ sources.x.flake = { flake = "/fixtures/top"; inherit fetchOnTarget; };
+ check = roots 3;
+ mutate = ''
+ readlink "$T"/x > "$T"/old
+ '';
+ }
+ {
+ sources.x.file = "/fixtures/alt";
+ check = ''
+ test ! -L "$T"/x || fail "x is still a link to $(readlink "$T"/x)"
+ test -e "$T"/x/flake.nix || fail "flake.nix missing"
+ nix-store --verify-path "$(cat "$T"/old)" || fail "store path changed"
+ '' + roots 0;
+ }
+ ];
+ })
] ++ lib.optional (targetKind == "local")
# The target has the path the input is locked to.
(s "uselocal" {