diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 6d46cf78a..2d0945927 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 @@ -368,10 +369,10 @@ Input Input::applyOverrides(std::optional ref, std::optional return scheme->applyOverrides(*this, ref, rev); } -void Input::clone(const Settings & settings, const 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 @@ -484,9 +485,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 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 17d147a7b..8fec57843 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -433,7 +433,8 @@ struct GitInputScheme : InputScheme return res; } - void clone(const Settings & settings, const Input & input, const 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 d108f66e7..6207e8ac0 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -457,12 +457,13 @@ struct GitHubInputScheme : GitArchiveInputScheme return DownloadUrl{parseURL(url), headers}; } - void clone(const Settings & settings, const Input & input, const 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); } }; @@ -544,7 +545,8 @@ struct GitLabInputScheme : GitArchiveInputScheme return DownloadUrl{parseURL(url), headers}; } - void clone(const Settings & settings, const Input & input, const 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 @@ -552,7 +554,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); } }; @@ -639,14 +641,15 @@ struct SourceHutInputScheme : GitArchiveInputScheme return DownloadUrl{parseURL(url), headers}; } - void clone(const Settings & settings, const Input & input, const 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 a4a87a5bd..ea76ecfac 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 Path & destDir) const; + void clone(const Settings & settings, ref store, const std::filesystem::path & destDir) const; std::optional getSourcePath() const; @@ -229,7 +229,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 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 b4d8b29bf..b1a52fc83 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1019,7 +1019,7 @@ struct CmdFlakeNew : CmdFlakeInitCommon struct CmdFlakeClone : FlakeCommand { - Path destDir; + std::filesystem::path destDir; std::string description() override { @@ -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- ]]