1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-19 23:09:37 +02:00

Merge remote-tracking branch 'upstream/master' into lfs

This commit is contained in:
Leandro Reina 2025-01-21 14:16:42 +01:00
commit 40a3007b7c
89 changed files with 904 additions and 459 deletions

View file

@ -70,6 +70,22 @@ struct Settings : public Config
Setting<bool> warnDirty{this, true, "warn-dirty",
"Whether to warn about dirty Git/Mercurial trees."};
Setting<bool> allowDirtyLocks{
this,
false,
"allow-dirty-locks",
R"(
Whether to allow dirty inputs (such as dirty Git workdirs)
to be locked via their NAR hash. This is generally bad
practice since Nix has no way to obtain such inputs if they
are subsequently modified. Therefore lock files with dirty
locks should generally only be used for local testing, and
should not be pushed to other users.
)",
{},
true,
Xp::Flakes};
Setting<bool> trustTarballsFromGitForges{
this, true, "trust-tarballs-from-git-forges",
R"(

View file

@ -4,6 +4,7 @@
#include "fetch-to-store.hh"
#include "json-utils.hh"
#include "store-path-accessor.hh"
#include "fetch-settings.hh"
#include <nlohmann/json.hpp>
@ -154,11 +155,23 @@ bool Input::isLocked() const
return scheme && scheme->isLocked(*this);
}
bool Input::isConsideredLocked(
const Settings & settings) const
{
return isLocked() || (settings.allowDirtyLocks && getNarHash());
}
bool Input::isFinal() const
{
return maybeGetBoolAttr(attrs, "__final").value_or(false);
}
std::optional<std::string> Input::isRelative() const
{
assert(scheme);
return scheme->isRelative(*this);
}
Attrs Input::toAttrs() const
{
return attrs;
@ -345,7 +358,7 @@ void Input::clone(const Path & destDir) const
scheme->clone(*this, destDir);
}
std::optional<Path> Input::getSourcePath() const
std::optional<std::filesystem::path> Input::getSourcePath() const
{
assert(scheme);
return scheme->getSourcePath(*this);
@ -448,7 +461,7 @@ Input InputScheme::applyOverrides(
return input;
}
std::optional<Path> InputScheme::getSourcePath(const Input & input) const
std::optional<std::filesystem::path> InputScheme::getSourcePath(const Input & input) const
{
return {};
}

View file

@ -41,11 +41,6 @@ struct Input
std::shared_ptr<InputScheme> scheme; // note: can be null
Attrs attrs;
/**
* path of the parent of this input, used for relative path resolution
*/
std::optional<Path> parent;
/**
* Cached result of getFingerprint().
*/
@ -95,6 +90,21 @@ public:
*/
bool isLocked() const;
/**
* Return whether the input is either locked, or, if
* `allow-dirty-locks` is enabled, it has a NAR hash. In the
* latter case, we can verify the input but we may not be able to
* fetch it from anywhere.
*/
bool isConsideredLocked(
const Settings & settings) const;
/**
* Only for relative path flakes, i.e. 'path:./foo', returns the
* relative path, i.e. './foo'.
*/
std::optional<std::string> isRelative() const;
/**
* Return whether this is a "final" input, meaning that fetching
* it will not add, remove or change any attributes. (See
@ -154,7 +164,7 @@ public:
void clone(const Path & destDir) const;
std::optional<Path> getSourcePath() const;
std::optional<std::filesystem::path> getSourcePath() const;
/**
* Write a file to this input, for input types that support
@ -237,7 +247,7 @@ struct InputScheme
virtual void clone(const Input & input, const Path & destDir) const;
virtual std::optional<Path> getSourcePath(const Input & input) const;
virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const;
virtual void putFile(
const Input & input,
@ -260,6 +270,9 @@ struct InputScheme
virtual bool isLocked(const Input & input) const
{ return false; }
virtual std::optional<std::string> isRelative(const Input & input) const
{ return std::nullopt; }
};
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);

View file

@ -300,7 +300,7 @@ struct GitInputScheme : InputScheme
Strings args = {"clone"};
args.push_back(repoInfo.url);
args.push_back(repoInfo.locationToArg());
if (auto ref = input.getRef()) {
args.push_back("--branch");
@ -314,11 +314,9 @@ struct GitInputScheme : InputScheme
runProgram("git", true, args, {}, true);
}
std::optional<Path> getSourcePath(const Input & input) const override
std::optional<std::filesystem::path> getSourcePath(const Input & input) const override
{
auto repoInfo = getRepoInfo(input);
if (repoInfo.isLocal) return repoInfo.url;
return std::nullopt;
return getRepoInfo(input).getPath();
}
void putFile(
@ -328,14 +326,15 @@ struct GitInputScheme : InputScheme
std::optional<std::string> commitMsg) const override
{
auto repoInfo = getRepoInfo(input);
if (!repoInfo.isLocal)
auto repoPath = repoInfo.getPath();
if (!repoPath)
throw Error("cannot commit '%s' to Git repository '%s' because it's not a working tree", path, input.to_string());
writeFile((CanonPath(repoInfo.url) / path).abs(), contents);
writeFile(*repoPath / path.rel(), contents);
auto result = runProgram(RunOptions {
.program = "git",
.args = {"-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "check-ignore", "--quiet", std::string(path.rel())},
.args = {"-C", repoPath->string(), "--git-dir", repoInfo.gitDir, "check-ignore", "--quiet", std::string(path.rel())},
});
auto exitCode =
#ifndef WIN32 // TODO abstract over exit status handling on Windows
@ -348,7 +347,7 @@ struct GitInputScheme : InputScheme
if (exitCode != 0) {
// The path is not `.gitignore`d, we can add the file.
runProgram("git", true,
{ "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "add", "--intent-to-add", "--", std::string(path.rel()) });
{ "-C", repoPath->string(), "--git-dir", repoInfo.gitDir, "add", "--intent-to-add", "--", std::string(path.rel()) });
if (commitMsg) {
@ -356,7 +355,7 @@ struct GitInputScheme : InputScheme
logger->pause();
Finally restoreLogger([]() { logger->resume(); });
runProgram("git", true,
{ "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "commit", std::string(path.rel()), "-F", "-" },
{ "-C", repoPath->string(), "--git-dir", repoInfo.gitDir, "commit", std::string(path.rel()), "-F", "-" },
*commitMsg);
}
}
@ -364,24 +363,41 @@ struct GitInputScheme : InputScheme
struct RepoInfo
{
/* Whether this is a local, non-bare repository. */
bool isLocal = false;
/* Either the path of the repo (for local, non-bare repos), or
the URL (which is never a `file` URL). */
std::variant<std::filesystem::path, ParsedURL> location;
/* Working directory info: the complete list of files, and
whether the working directory is dirty compared to HEAD. */
GitRepo::WorkdirInfo workdirInfo;
/* URL of the repo, or its path if isLocal. Never a `file` URL. */
std::string url;
std::string locationToArg() const
{
return std::visit(
overloaded {
[&](const std::filesystem::path & path)
{ return path.string(); },
[&](const ParsedURL & url)
{ return url.to_string(); }
}, location);
}
std::optional<std::filesystem::path> getPath() const
{
if (auto path = std::get_if<std::filesystem::path>(&location))
return *path;
else
return std::nullopt;
}
void warnDirty(const Settings & settings) const
{
if (workdirInfo.isDirty) {
if (!settings.allowDirty)
throw Error("Git tree '%s' is dirty", url);
throw Error("Git tree '%s' is dirty", locationToArg());
if (settings.warnDirty)
warn("Git tree '%s' is dirty", url);
warn("Git tree '%s' is dirty", locationToArg());
}
}
@ -433,18 +449,36 @@ struct GitInputScheme : InputScheme
static bool forceHttp = getEnv("_NIX_FORCE_HTTP") == "1"; // for testing
auto url = parseURL(getStrAttr(input.attrs, "url"));
bool isBareRepository = url.scheme == "file" && !pathExists(url.path + "/.git");
repoInfo.isLocal = url.scheme == "file" && !forceHttp && !isBareRepository;
repoInfo.url = repoInfo.isLocal ? url.path : url.to_string();
//
// FIXME: here we turn a possibly relative path into an absolute path.
// This allows relative git flake inputs to be resolved against the
// **current working directory** (as in POSIX), which tends to work out
// ok in the context of flakes, but is the wrong behavior,
// as it should resolve against the flake.nix base directory instead.
//
// See: https://discourse.nixos.org/t/57783 and #9708
//
if (url.scheme == "file" && !forceHttp && !isBareRepository) {
if (!isAbsolute(url.path)) {
warn(
"Fetching Git repository '%s', which uses a path relative to the current directory. "
"This is not supported and will stop working in a future release. "
"See https://github.com/NixOS/nix/issues/12281 for details.",
url);
}
repoInfo.location = std::filesystem::absolute(url.path);
} else
repoInfo.location = url;
// If this is a local directory and no ref or revision is
// given, then allow the use of an unclean working tree.
if (!input.getRef() && !input.getRev() && repoInfo.isLocal)
repoInfo.workdirInfo = GitRepo::getCachedWorkdirInfo(repoInfo.url);
if (auto repoPath = repoInfo.getPath(); !input.getRef() && !input.getRev() && repoPath)
repoInfo.workdirInfo = GitRepo::getCachedWorkdirInfo(*repoPath);
return repoInfo;
}
uint64_t getLastModified(const RepoInfo & repoInfo, const std::string & repoDir, const Hash & rev) const
uint64_t getLastModified(const RepoInfo & repoInfo, const std::filesystem::path & repoDir, const Hash & rev) const
{
Cache::Key key{"gitLastModified", {{"rev", rev.gitRev()}}};
@ -460,7 +494,7 @@ struct GitInputScheme : InputScheme
return lastModified;
}
uint64_t getRevCount(const RepoInfo & repoInfo, const std::string & repoDir, const Hash & rev) const
uint64_t getRevCount(const RepoInfo & repoInfo, const std::filesystem::path & repoDir, const Hash & rev) const
{
Cache::Key key{"gitRevCount", {{"rev", rev.gitRev()}}};
@ -469,7 +503,7 @@ struct GitInputScheme : InputScheme
if (auto revCountAttrs = cache->lookup(key))
return getIntAttr(*revCountAttrs, "revCount");
Activity act(*logger, lvlChatty, actUnknown, fmt("getting Git revision count of '%s'", repoInfo.url));
Activity act(*logger, lvlChatty, actUnknown, fmt("getting Git revision count of '%s'", repoInfo.locationToArg()));
auto revCount = GitRepo::openRepo(repoDir)->getRevCount(rev);
@ -480,11 +514,15 @@ struct GitInputScheme : InputScheme
std::string getDefaultRef(const RepoInfo & repoInfo) const
{
auto head = repoInfo.isLocal
? GitRepo::openRepo(repoInfo.url)->getWorkdirRef()
: readHeadCached(repoInfo.url);
auto head = std::visit(
overloaded {
[&](const std::filesystem::path & path)
{ return GitRepo::openRepo(path)->getWorkdirRef(); },
[&](const ParsedURL & url)
{ return readHeadCached(url.to_string()); }
}, repoInfo.location);
if (!head) {
warn("could not read HEAD ref from repo at '%s', using 'master'", repoInfo.url);
warn("could not read HEAD ref from repo at '%s', using 'master'", repoInfo.locationToArg());
return "master";
}
return *head;
@ -527,29 +565,30 @@ struct GitInputScheme : InputScheme
auto ref = originalRef ? *originalRef : getDefaultRef(repoInfo);
input.attrs.insert_or_assign("ref", ref);
Path repoDir;
std::filesystem::path repoDir;
if (repoInfo.isLocal) {
repoDir = repoInfo.url;
if (auto repoPath = repoInfo.getPath()) {
repoDir = *repoPath;
if (!input.getRev())
input.attrs.insert_or_assign("rev", GitRepo::openRepo(repoDir)->resolveRef(ref).gitRev());
} else {
Path cacheDir = getCachePath(repoInfo.url, getShallowAttr(input));
auto repoUrl = std::get<ParsedURL>(repoInfo.location);
std::filesystem::path cacheDir = getCachePath(repoUrl.to_string(), getShallowAttr(input));
repoDir = cacheDir;
repoInfo.gitDir = ".";
createDirs(dirOf(cacheDir));
PathLocks cacheDirLock({cacheDir});
std::filesystem::create_directories(cacheDir.parent_path());
PathLocks cacheDirLock({cacheDir.string()});
auto repo = GitRepo::openRepo(cacheDir, true, true);
// We need to set the origin so resolving submodule URLs works
repo->setRemote("origin", repoInfo.url);
repo->setRemote("origin", repoUrl.to_string());
Path localRefFile =
auto localRefFile =
ref.compare(0, 5, "refs/") == 0
? cacheDir + "/" + ref
: cacheDir + "/refs/heads/" + ref;
? cacheDir / ref
: cacheDir / "refs/heads" / ref;
bool doFetch;
time_t now = time(0);
@ -565,7 +604,7 @@ struct GitInputScheme : InputScheme
/* If the local ref is older than tarball-ttl seconds, do a
git fetch to update the local ref to the remote ref. */
struct stat st;
doFetch = stat(localRefFile.c_str(), &st) != 0 ||
doFetch = stat(localRefFile.string().c_str(), &st) != 0 ||
!isCacheFileWithinTtl(now, st);
}
}
@ -583,11 +622,11 @@ struct GitInputScheme : InputScheme
? ref
: "refs/heads/" + ref;
repo->fetch(repoInfo.url, fmt("%s:%s", fetchRef, fetchRef), getShallowAttr(input));
repo->fetch(repoUrl.to_string(), fmt("%s:%s", fetchRef, fetchRef), getShallowAttr(input));
} catch (Error & e) {
if (!pathExists(localRefFile)) throw;
if (!std::filesystem::exists(localRefFile)) throw;
logError(e.info());
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", repoInfo.url);
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", repoInfo.locationToArg());
}
try {
@ -596,8 +635,8 @@ struct GitInputScheme : InputScheme
} catch (Error & e) {
warn("could not update mtime for file '%s': %s", localRefFile, e.info().msg);
}
if (!originalRef && !storeCachedHead(repoInfo.url, ref))
warn("could not update cached head '%s' for '%s'", ref, repoInfo.url);
if (!originalRef && !storeCachedHead(repoUrl.to_string(), ref))
warn("could not update cached head '%s' for '%s'", ref, repoInfo.locationToArg());
}
if (auto rev = input.getRev()) {
@ -609,8 +648,7 @@ struct GitInputScheme : InputScheme
"allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".",
rev->gitRev(),
ref,
repoInfo.url
);
repoInfo.locationToArg());
} else
input.attrs.insert_or_assign("rev", repo->resolveRef(ref).gitRev());
@ -622,7 +660,7 @@ struct GitInputScheme : InputScheme
auto isShallow = repo->isShallow();
if (isShallow && !getShallowAttr(input))
throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified", repoInfo.url);
throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified", repoInfo.locationToArg());
// FIXME: check whether rev is an ancestor of ref?
@ -637,7 +675,7 @@ struct GitInputScheme : InputScheme
infoAttrs.insert_or_assign("revCount",
getRevCount(repoInfo, repoDir, rev));
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.url);
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
verifyCommit(input, repo);
@ -693,21 +731,23 @@ struct GitInputScheme : InputScheme
RepoInfo & repoInfo,
Input && input) const
{
auto repoPath = repoInfo.getPath().value();
if (getSubmodulesAttr(input))
/* Create mountpoints for the submodules. */
for (auto & submodule : repoInfo.workdirInfo.submodules)
repoInfo.workdirInfo.files.insert(submodule.path);
auto repo = GitRepo::openRepo(repoInfo.url, false, false);
auto repo = GitRepo::openRepo(repoPath, false, false);
auto exportIgnore = getExportIgnoreAttr(input);
ref<SourceAccessor> accessor =
repo->getAccessor(repoInfo.workdirInfo,
exportIgnore,
makeNotAllowedError(repoInfo.url));
makeNotAllowedError(repoInfo.locationToArg()));
accessor->setPathDisplay(repoInfo.url);
accessor->setPathDisplay(repoInfo.locationToArg());
/* If the repo has submodules, return a mounted input accessor
consisting of the accessor for the top-level repo and the
@ -716,10 +756,10 @@ struct GitInputScheme : InputScheme
std::map<CanonPath, nix::ref<SourceAccessor>> mounts;
for (auto & submodule : repoInfo.workdirInfo.submodules) {
auto submodulePath = CanonPath(repoInfo.url) / submodule.path;
auto submodulePath = repoPath / submodule.path.rel();
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "git");
attrs.insert_or_assign("url", submodulePath.abs());
attrs.insert_or_assign("url", submodulePath.string());
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
@ -743,7 +783,7 @@ struct GitInputScheme : InputScheme
}
if (!repoInfo.workdirInfo.isDirty) {
auto repo = GitRepo::openRepo(repoInfo.url);
auto repo = GitRepo::openRepo(repoPath);
if (auto ref = repo->getWorkdirRef())
input.attrs.insert_or_assign("ref", *ref);
@ -753,7 +793,7 @@ struct GitInputScheme : InputScheme
input.attrs.insert_or_assign("rev", rev.gitRev());
input.attrs.insert_or_assign("revCount",
rev == nullRev ? 0 : getRevCount(repoInfo, repoInfo.url, rev));
rev == nullRev ? 0 : getRevCount(repoInfo, repoPath, rev));
verifyCommit(input, repo);
} else {
@ -772,7 +812,7 @@ struct GitInputScheme : InputScheme
input.attrs.insert_or_assign(
"lastModified",
repoInfo.workdirInfo.headRev
? getLastModified(repoInfo, repoInfo.url, *repoInfo.workdirInfo.headRev)
? getLastModified(repoInfo, repoPath, *repoInfo.workdirInfo.headRev)
: 0);
return {accessor, std::move(input)};
@ -795,7 +835,7 @@ struct GitInputScheme : InputScheme
}
auto [accessor, final] =
input.getRef() || input.getRev() || !repoInfo.isLocal
input.getRef() || input.getRev() || !repoInfo.getPath()
? getAccessorFromCommit(store, repoInfo, std::move(input))
: getAccessorFromWorkdir(store, repoInfo, std::move(input));
@ -813,14 +853,14 @@ struct GitInputScheme : InputScheme
return makeFingerprint(*rev);
else {
auto repoInfo = getRepoInfo(input);
if (repoInfo.isLocal && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) {
if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) {
/* Calculate a fingerprint that takes into account the
deleted and modified/added files. */
HashSink hashSink{HashAlgorithm::SHA512};
for (auto & file : repoInfo.workdirInfo.dirtyFiles) {
writeString("modified:", hashSink);
writeString(file.abs(), hashSink);
dumpPath(repoInfo.url + "/" + file.abs(), hashSink);
dumpPath((*repoPath / file.rel()).string(), hashSink);
}
for (auto & file : repoInfo.workdirInfo.deletedFiles) {
writeString("deleted:", hashSink);

View file

@ -126,7 +126,7 @@ struct MercurialInputScheme : InputScheme
return res;
}
std::optional<Path> getSourcePath(const Input & input) const override
std::optional<std::filesystem::path> getSourcePath(const Input & input) const override
{
auto url = parseURL(getStrAttr(input.attrs, "url"));
if (url.scheme == "file" && !input.getRef() && !input.getRev())

View file

@ -1,5 +1,4 @@
{ lib
, stdenv
, mkMesonLibrary
, nix-util
@ -51,10 +50,6 @@ mkMesonLibrary (finalAttrs: {
echo ${version} > ../../.version
'';
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};

View file

@ -80,9 +80,9 @@ struct PathInputScheme : InputScheme
};
}
std::optional<Path> getSourcePath(const Input & input) const override
std::optional<std::filesystem::path> getSourcePath(const Input & input) const override
{
return getStrAttr(input.attrs, "path");
return getAbsPath(input);
}
void putFile(
@ -91,13 +91,13 @@ struct PathInputScheme : InputScheme
std::string_view contents,
std::optional<std::string> commitMsg) const override
{
writeFile((CanonPath(getAbsPath(input)) / path).abs(), contents);
writeFile(getAbsPath(input) / path.rel(), contents);
}
std::optional<std::string> isRelative(const Input & input) const
std::optional<std::string> isRelative(const Input & input) const override
{
auto path = getStrAttr(input.attrs, "path");
if (hasPrefix(path, "/"))
if (isAbsolute(path))
return std::nullopt;
else
return path;
@ -108,12 +108,12 @@ struct PathInputScheme : InputScheme
return (bool) input.getNarHash();
}
CanonPath getAbsPath(const Input & input) const
std::filesystem::path getAbsPath(const Input & input) const
{
auto path = getStrAttr(input.attrs, "path");
if (path[0] == '/')
return CanonPath(path);
if (isAbsolute(path))
return canonPath(path);
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
}
@ -121,31 +121,14 @@ struct PathInputScheme : InputScheme
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
{
Input input(_input);
std::string absPath;
auto path = getStrAttr(input.attrs, "path");
if (path[0] != '/') {
if (!input.parent)
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
auto absPath = getAbsPath(input);
auto parent = canonPath(*input.parent);
// the path isn't relative, prefix it
absPath = nix::absPath(path, parent);
// for security, ensure that if the parent is a store path, it's inside it
if (store->isInStore(parent)) {
auto storePath = store->printStorePath(store->toStorePath(parent).first);
if (!isDirOrInDir(absPath, storePath))
throw BadStorePath("relative path '%s' points outside of its parent's store path '%s'", path, storePath);
}
} else
absPath = path;
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s'", absPath));
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to the store", absPath));
// FIXME: check whether access to 'path' is allowed.
auto storePath = store->maybeParseStorePath(absPath);
auto storePath = store->maybeParseStorePath(absPath.string());
if (storePath)
store->addTempRoot(*storePath);
@ -154,7 +137,7 @@ struct PathInputScheme : InputScheme
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
// FIXME: try to substitute storePath.
auto src = sinkToSource([&](Sink & sink) {
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter);
});
storePath = store->addToStoreFromDump(*src, "source");
}
@ -176,7 +159,7 @@ struct PathInputScheme : InputScheme
store object and the subpath. */
auto path = getAbsPath(input);
try {
auto [storePath, subPath] = store->toStorePath(path.abs());
auto [storePath, subPath] = store->toStorePath(path.string());
auto info = store->queryPathInfo(storePath);
return fmt("path:%s:%s", info->narHash.to_string(HashFormat::Base16, false), subPath);
} catch (Error &) {

View file

@ -153,7 +153,7 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, re
return std::make_shared<Registry>(settings, Registry::Global); // empty registry
}
if (!hasPrefix(path, "/")) {
if (!isAbsolute(path)) {
auto storePath = downloadFile(store, path, "flake-registry.json").storePath;
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
store2->addPermRoot(storePath, getCacheDir() + "/flake-registry.json");