From d00682beb2fc0ba62fb87752ba429cc0f4c4345e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 8 May 2025 19:01:34 +0200 Subject: [PATCH] Backward compatibility hack for dealing with `dir` in URL-style flakerefs --- src/libflake/flake.cc | 2 +- src/libflake/flakeref.cc | 49 ++++++++++++++++++ src/libflake/include/nix/flake/flakeref.hh | 6 +++ tests/functional/flakes/meson.build | 1 + tests/functional/flakes/old-lockfiles.sh | 60 ++++++++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 tests/functional/flakes/old-lockfiles.sh diff --git a/src/libflake/flake.cc b/src/libflake/flake.cc index 516b38131..987c9f610 100644 --- a/src/libflake/flake.cc +++ b/src/libflake/flake.cc @@ -595,7 +595,7 @@ LockedFlake lockFlake( oldLock = *oldLock3; if (oldLock - && oldLock->originalRef == *input.ref + && oldLock->originalRef.canonicalize() == input.ref->canonicalize() && oldLock->parentInputAttrPath == overridenParentPath && !hasCliOverride) { diff --git a/src/libflake/flakeref.cc b/src/libflake/flakeref.cc index a8b139d65..12bddf578 100644 --- a/src/libflake/flakeref.cc +++ b/src/libflake/flakeref.cc @@ -289,6 +289,55 @@ std::pair, FlakeRef> FlakeRef::lazyFetch(ref store) c return {accessor, FlakeRef(std::move(lockedInput), subdir)}; } +FlakeRef FlakeRef::canonicalize() const +{ + auto flakeRef(*this); + + /* Backward compatibility hack: In old versions of Nix, if you had + a flake input like + + inputs.foo.url = "git+https://foo/bar?dir=subdir"; + + it would result in a lock file entry like + + "original": { + "dir": "subdir", + "type": "git", + "url": "https://foo/bar?dir=subdir" + } + + New versions of Nix remove `?dir=subdir` from the `url` field, + since the subdirectory is intended for `FlakeRef`, not the + fetcher (and specifically the remote server), that is, the + flakeref is parsed into + + "original": { + "dir": "subdir", + "type": "git", + "url": "https://foo/bar" + } + + However, this causes new versions of Nix to consider the lock + file entry to be stale since the `original` ref no longer + matches exactly. + + For this reason, we canonicalise the `original` ref by + filtering the `dir` query parameter from the URL. */ + if (auto url = fetchers::maybeGetStrAttr(flakeRef.input.attrs, "url")) { + try { + auto parsed = parseURL(*url); + if (auto dir2 = get(parsed.query, "dir")) { + if (flakeRef.subdir != "" && flakeRef.subdir == *dir2) + parsed.query.erase("dir"); + } + flakeRef.input.attrs.insert_or_assign("url", parsed.to_string()); + } catch (BadURL &) { + } + } + + return flakeRef; +} + std::tuple parseFlakeRefWithFragmentAndExtendedOutputsSpec( const fetchers::Settings & fetchSettings, const std::string & url, diff --git a/src/libflake/include/nix/flake/flakeref.hh b/src/libflake/include/nix/flake/flakeref.hh index 8c15f9d95..6184d2363 100644 --- a/src/libflake/include/nix/flake/flakeref.hh +++ b/src/libflake/include/nix/flake/flakeref.hh @@ -72,6 +72,12 @@ struct FlakeRef const fetchers::Attrs & attrs); std::pair, FlakeRef> lazyFetch(ref store) const; + + /** + * Canonicalize a flakeref for the purpose of comparing "old" and + * "new" `original` fields in lock files. + */ + FlakeRef canonicalize() const; }; std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef); diff --git a/tests/functional/flakes/meson.build b/tests/functional/flakes/meson.build index 368c43876..213c388a6 100644 --- a/tests/functional/flakes/meson.build +++ b/tests/functional/flakes/meson.build @@ -32,6 +32,7 @@ suites += { 'symlink-paths.sh', 'debugger.sh', 'source-paths.sh', + 'old-lockfiles.sh', ], 'workdir': meson.current_source_dir(), } diff --git a/tests/functional/flakes/old-lockfiles.sh b/tests/functional/flakes/old-lockfiles.sh new file mode 100644 index 000000000..fd36abdcc --- /dev/null +++ b/tests/functional/flakes/old-lockfiles.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +source ./common.sh + +requireGit + +repo="$TEST_ROOT/repo" + +createGitRepo "$repo" + +cat > "$repo/flake.nix" < "$repo/flake.lock" <