From d07c24f4c880fdec207c6e0cd1b803d6a2fc2ff9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 17 Nov 2025 19:27:45 +0100 Subject: [PATCH] nix flake clone: Support all input types For input types that have no concept of cloning, we now default to copying the entire source tree. --- src/libfetchers/fetchers.cc | 19 +++++++++++++++---- src/libfetchers/git.cc | 3 ++- src/libfetchers/github.cc | 15 +++++++++------ .../include/nix/fetchers/fetchers.hh | 5 +++-- src/nix/flake.cc | 2 +- tests/functional/flakes/flakes.sh | 4 ++++ 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index ab7b50ea7..9ebad6220 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -6,6 +6,7 @@ #include "nix/fetchers/fetch-settings.hh" #include "nix/fetchers/fetch-to-store.hh" #include "nix/util/url.hh" +#include "nix/util/archive.hh" #include @@ -377,10 +378,10 @@ Input Input::applyOverrides(std::optional ref, std::optional return scheme->applyOverrides(*this, ref, rev); } -void Input::clone(const Settings & settings, const std::filesystem::path & destDir) const +void Input::clone(const Settings & settings, ref store, const std::filesystem::path & destDir) const { assert(scheme); - scheme->clone(settings, *this, destDir); + scheme->clone(settings, store, *this, destDir); } std::optional Input::getSourcePath() const @@ -493,9 +494,19 @@ void InputScheme::putFile( throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path); } -void InputScheme::clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const +void InputScheme::clone( + const Settings & settings, ref store, const Input & input, const std::filesystem::path & destDir) const { - throw Error("do not know how to clone input '%s'", input.to_string()); + if (std::filesystem::exists(destDir)) + throw Error("cannot clone into existing path %s", destDir); + + auto [accessor, input2] = getAccessor(settings, store, input); + + Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to %s...", input2.to_string(), destDir)); + + auto source = sinkToSource([&](Sink & sink) { accessor->dumpPath(CanonPath::root, sink); }); + + restorePath(destDir, *source); } std::optional InputScheme::experimentalFeature() const diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index b10d6d0bb..1f926a86c 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -278,7 +278,8 @@ struct GitInputScheme : InputScheme return res; } - void clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const override + void clone(const Settings & settings, ref store, const Input & input, const std::filesystem::path & destDir) + const override { auto repoInfo = getRepoInfo(input); diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index af633dda1..1d8e0225e 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -426,12 +426,13 @@ struct GitHubInputScheme : GitArchiveInputScheme return DownloadUrl{parseURL(url), headers}; } - void clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const override + void clone(const Settings & settings, ref store, const Input & input, const std::filesystem::path & destDir) + const override { auto host = getHost(input); Input::fromURL(settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input))) .applyOverrides(input.getRef(), input.getRev()) - .clone(settings, destDir); + .clone(settings, store, destDir); } }; @@ -507,7 +508,8 @@ struct GitLabInputScheme : GitArchiveInputScheme return DownloadUrl{parseURL(url), headers}; } - void clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const override + void clone(const Settings & settings, ref store, const Input & input, const std::filesystem::path & destDir) + const override { auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); // FIXME: get username somewhere @@ -515,7 +517,7 @@ struct GitLabInputScheme : GitArchiveInputScheme settings, fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) .applyOverrides(input.getRef(), input.getRev()) - .clone(settings, destDir); + .clone(settings, store, destDir); } }; @@ -596,14 +598,15 @@ struct SourceHutInputScheme : GitArchiveInputScheme return DownloadUrl{parseURL(url), headers}; } - void clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const override + void clone(const Settings & settings, ref store, const Input & input, const std::filesystem::path & destDir) + const override { auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); Input::fromURL( settings, fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) .applyOverrides(input.getRef(), input.getRev()) - .clone(settings, destDir); + .clone(settings, store, destDir); } }; diff --git a/src/libfetchers/include/nix/fetchers/fetchers.hh b/src/libfetchers/include/nix/fetchers/fetchers.hh index 978fd2276..1c126564f 100644 --- a/src/libfetchers/include/nix/fetchers/fetchers.hh +++ b/src/libfetchers/include/nix/fetchers/fetchers.hh @@ -143,7 +143,7 @@ public: Input applyOverrides(std::optional ref, std::optional rev) const; - void clone(const Settings & settings, const std::filesystem::path & destDir) const; + void clone(const Settings & settings, ref store, const std::filesystem::path & destDir) const; std::optional getSourcePath() const; @@ -216,7 +216,8 @@ struct InputScheme virtual Input applyOverrides(const Input & input, std::optional ref, std::optional rev) const; - virtual void clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const; + virtual void clone( + const Settings & settings, ref store, const Input & input, const std::filesystem::path & destDir) const; virtual std::optional getSourcePath(const Input & input) const; diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 80cad095f..b1a52fc83 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1049,7 +1049,7 @@ struct CmdFlakeClone : FlakeCommand if (destDir.empty()) throw Error("missing flag '--dest'"); - getFlakeRef().resolve(fetchSettings, store).input.clone(fetchSettings, destDir); + getFlakeRef().resolve(fetchSettings, store).input.clone(fetchSettings, store, destDir); } }; diff --git a/tests/functional/flakes/flakes.sh b/tests/functional/flakes/flakes.sh index 5b1da0f02..9383cc3ad 100755 --- a/tests/functional/flakes/flakes.sh +++ b/tests/functional/flakes/flakes.sh @@ -369,6 +369,10 @@ tar cfz "$TEST_ROOT"/flake.tar.gz -C "$TEST_ROOT" flake5 nix build -o "$TEST_ROOT"/result file://"$TEST_ROOT"/flake.tar.gz +nix flake clone "file://$TEST_ROOT/flake.tar.gz" --dest "$TEST_ROOT/unpacked" +[[ -e $TEST_ROOT/unpacked/flake.nix ]] +expectStderr 1 nix flake clone "file://$TEST_ROOT/flake.tar.gz" --dest "$TEST_ROOT/unpacked" | grep 'existing path' + # Building with a tarball URL containing a SRI hash should also work. url=$(nix flake metadata --json file://"$TEST_ROOT"/flake.tar.gz | jq -r .url) [[ $url =~ sha256- ]]