1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-18 00:12:43 +01:00

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.
This commit is contained in:
Eelco Dolstra 2025-11-17 19:27:45 +01:00
parent 95da93c05b
commit d07c24f4c8
6 changed files with 34 additions and 14 deletions

View file

@ -6,6 +6,7 @@
#include "nix/fetchers/fetch-settings.hh" #include "nix/fetchers/fetch-settings.hh"
#include "nix/fetchers/fetch-to-store.hh" #include "nix/fetchers/fetch-to-store.hh"
#include "nix/util/url.hh" #include "nix/util/url.hh"
#include "nix/util/archive.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -377,10 +378,10 @@ Input Input::applyOverrides(std::optional<std::string> ref, std::optional<Hash>
return scheme->applyOverrides(*this, ref, rev); 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> store, const std::filesystem::path & destDir) const
{ {
assert(scheme); assert(scheme);
scheme->clone(settings, *this, destDir); scheme->clone(settings, store, *this, destDir);
} }
std::optional<std::filesystem::path> Input::getSourcePath() const std::optional<std::filesystem::path> Input::getSourcePath() const
@ -493,9 +494,19 @@ void InputScheme::putFile(
throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path); 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> 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<ExperimentalFeature> InputScheme::experimentalFeature() const std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const

View file

@ -278,7 +278,8 @@ struct GitInputScheme : InputScheme
return res; return res;
} }
void clone(const Settings & settings, const Input & input, const std::filesystem::path & destDir) const override void clone(const Settings & settings, ref<Store> store, const Input & input, const std::filesystem::path & destDir)
const override
{ {
auto repoInfo = getRepoInfo(input); auto repoInfo = getRepoInfo(input);

View file

@ -426,12 +426,13 @@ struct GitHubInputScheme : GitArchiveInputScheme
return DownloadUrl{parseURL(url), headers}; 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> store, const Input & input, const std::filesystem::path & destDir)
const override
{ {
auto host = getHost(input); auto host = getHost(input);
Input::fromURL(settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input))) Input::fromURL(settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(settings, destDir); .clone(settings, store, destDir);
} }
}; };
@ -507,7 +508,8 @@ struct GitLabInputScheme : GitArchiveInputScheme
return DownloadUrl{parseURL(url), headers}; 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> store, const Input & input, const std::filesystem::path & destDir)
const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
// FIXME: get username somewhere // FIXME: get username somewhere
@ -515,7 +517,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
settings, settings,
fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(settings, destDir); .clone(settings, store, destDir);
} }
}; };
@ -596,14 +598,15 @@ struct SourceHutInputScheme : GitArchiveInputScheme
return DownloadUrl{parseURL(url), headers}; 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> store, const Input & input, const std::filesystem::path & destDir)
const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
Input::fromURL( Input::fromURL(
settings, settings,
fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(settings, destDir); .clone(settings, store, destDir);
} }
}; };

View file

@ -143,7 +143,7 @@ public:
Input applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const; Input applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const;
void clone(const Settings & settings, const std::filesystem::path & destDir) const; void clone(const Settings & settings, ref<Store> store, const std::filesystem::path & destDir) const;
std::optional<std::filesystem::path> getSourcePath() const; std::optional<std::filesystem::path> getSourcePath() const;
@ -216,7 +216,8 @@ struct InputScheme
virtual Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const; virtual Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> 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> store, const Input & input, const std::filesystem::path & destDir) const;
virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const; virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const;

View file

@ -1049,7 +1049,7 @@ struct CmdFlakeClone : FlakeCommand
if (destDir.empty()) if (destDir.empty())
throw Error("missing flag '--dest'"); throw Error("missing flag '--dest'");
getFlakeRef().resolve(fetchSettings, store).input.clone(fetchSettings, destDir); getFlakeRef().resolve(fetchSettings, store).input.clone(fetchSettings, store, destDir);
} }
}; };

View file

@ -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 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. # 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=$(nix flake metadata --json file://"$TEST_ROOT"/flake.tar.gz | jq -r .url)
[[ $url =~ sha256- ]] [[ $url =~ sha256- ]]