aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-13 20:28:00 +0000
committertv <tv@krebsco.de>2026-11-13 21:59:13 +0100
commit24fd14b1e6de45def6a59b65486f5035ac486ee3 (patch)
treeff9928227578c3befa83bfcdee41548aa978efe8
parentb34f53d538c5afa95fd0dc6dd81b56e4995b80c2 (diff)
populate: refer to flakes as git+file: only with a usable .git
Writing the lock file of a git+file: flake, Nix runs Git, with the repository's configuration and hooks, which may run commands. A .git in a source's directory can come from anywhere, e.g. one planted in a materialized flake, or one a file source keeps. So drawing inputs refers to a flake as git+file: only if git_problems finds nothing wrong with its .git directory, the way git sources check their work trees, and else as path:. lock-foreign-git plants a .git in a materialized flake and in a file source that keeps it, whose configuration names an fsmonitor command.
-rw-r--r--CHANGELOG.md2
-rw-r--r--README.md5
-rw-r--r--pkgs/populate/src/flake.rs6
-rw-r--r--pkgs/populate/src/sh.rs11
-rw-r--r--tests/scenarios.nix45
5 files changed, 63 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e19decc..242bca3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@
- `git` sources also refuse work trees whose `.git` directory contains
hooks, or in which a submodule's `.git` isn't a file naming a repository
in `.git/modules`, unless `reclone` is set.
+- Drawing inputs refers to a source in a Git work tree as `git+file:` only
+ if a `git` source would use its `.git` directory, and else as `path:`.
- `git` sources with a branch or tag as `ref` follow it: populate fetches
the branch or tag on every run, and resets the work tree to what it
fetched.
diff --git a/README.md b/README.md
index e46ef4b..94568f6 100644
--- a/README.md
+++ b/README.md
@@ -724,7 +724,10 @@ drawing an input from a source with a generated flake uses that flake.
automatically.
Sources in a Git work tree are referred to as `git+file:` (with submodules if
-there are any), all other sources as `path:`, with symlinks resolved. Lock
+there are any), all other sources as `path:`, with symlinks resolved. Nix
+runs Git in a `git+file:` flake, so that's only for work trees whose `.git`
+directory a `git` source would use, see
+[The target directory](#the-target-directory). Lock
files get rewritten or generated on every run, so they match the sources.
Copies of `flake` and `flake-input` sources get made again only if the
delivered store path, the patches, or the sources they draw inputs from
diff --git a/pkgs/populate/src/flake.rs b/pkgs/populate/src/flake.rs
index 26373d4..ded7692 100644
--- a/pkgs/populate/src/flake.rs
+++ b/pkgs/populate/src/flake.rs
@@ -228,8 +228,9 @@ impl<'a> Flakes<'a> {
String::new()
};
format!(
- "{}{}{require}{snippet}",
+ "{}{}{}{require}{snippet}",
sh::prelude(),
+ sh::GIT_PROBLEMS,
sh::draw::DEFINITIONS
)
}
@@ -270,7 +271,8 @@ impl<'a> Flakes<'a> {
.collect())
}
- /// The flake URL of the source named name, at resolved.
+ /// The flake URL of the source named name, at resolved: git+file: if
+ /// it has a .git directory populate can use, else path:.
fn flake_url(name: &str, resolved: &Resolved) -> Result<String> {
if !resolved.dir {
return Err(Failure::Message(format!(
diff --git a/pkgs/populate/src/sh.rs b/pkgs/populate/src/sh.rs
index f76a0f7..15da78c 100644
--- a/pkgs/populate/src/sh.rs
+++ b/pkgs/populate/src/sh.rs
@@ -812,14 +812,19 @@ use_state "$@"
/// resolve PATH...: prints, for each PATH, what's there and where it
/// resolves to, as two fields, each ending in a null byte. The first
- /// holds d if PATH is a directory, g if it has .git, s if it has
- /// .gitmodules, and f if it has flake.nix.
+ /// holds d if PATH is a directory, g if it has a .git directory that
+ /// git_problems finds nothing wrong with, s if it has .gitmodules, and
+ /// f if it has flake.nix. Nix runs Git in a git+file: flake, with the
+ /// repository's configuration and hooks, so it only gets such ones.
pub const RESOLVE: &str = r#"
for path; do
resolved=$(readlink -f "$path") || resolved=
flags=
if test -d "$path"; then flags=${flags}d; fi
- if test -e "$path"/.git; then flags=${flags}g; fi
+ if ! test -L "$path"/.git && test -d "$path"/.git; then
+ problems=$(git_problems "$path" 2>/dev/null) || problems=failed
+ if test -z "$problems"; then flags=${flags}g; fi
+ fi
if test -e "$path"/.gitmodules; then flags=${flags}s; fi
if test -e "$path"/flake.nix; then flags=${flags}f; fi
printf '%s\0%s\0' "$flags" "$resolved"
diff --git a/tests/scenarios.nix b/tests/scenarios.nix
index d12dcd1..15d0a53 100644
--- a/tests/scenarios.nix
+++ b/tests/scenarios.nix
@@ -2188,6 +2188,51 @@ assert !(krops.isLocalTarget (targets.remote "/"));
}
];
}))
+ # A .git that a materialized flake got, and one that a file source
+ # keeps, which Nix uses for the git+file: URL of the flake there: writing
+ # its lock file, Nix runs Git, with that repository's configuration.
+ (s "lock-foreign-git" (let
+ elsewhere = "/var/krops-elsewhere";
+ sources = alt // {
+ x.flake-input = {
+ url = "/fixtures/top"; name = "dep"; inherit fetchOnTarget;
+ materialize = true;
+ inputs.inner.follows = "alt";
+ };
+ y.file = {
+ path = "/fixtures/filesrc";
+ deleteExcluded = false;
+ filters = [ { type = "exclude"; pattern = "/.git"; } ];
+ inputs.dep.follows = "alt";
+ };
+ };
+ in {
+ isolate = true;
+ prepare = ''
+ rm -fR ${elsewhere}
+ mkdir -p ${elsewhere}
+ '';
+ phases = [
+ {
+ inherit sources;
+ # Repositories tracking only flake.nix, so the lock file changes.
+ mutate = ''
+ for name in x y; do
+ git -C "$T"/$name init -q
+ git -C "$T"/$name add flake.nix
+ git -C "$T"/$name -c user.name=x -c user.email=x@x commit -q -m x
+ git -C "$T"/$name config core.fsmonitor "touch ${elsewhere}/ran; false"
+ done
+ '';
+ }
+ {
+ inherit sources;
+ check = ''
+ test ! -e ${elsewhere}/ran || fail "a command in a .git directory ran"
+ '';
+ }
+ ];
+ }))
# A file in populate's state replaced by a symlink to a file outside.
(s "state-key" (let
sources.x.flake-input = {