2019-02-12 19:23:11 +02:00
|
|
|
#include "flakeref.hh"
|
2019-05-31 20:01:11 +03:00
|
|
|
#include "store-api.hh"
|
2020-01-21 17:27:53 +02:00
|
|
|
#include "fetchers/parse.hh"
|
|
|
|
#include "fetchers/fetchers.hh"
|
|
|
|
#include "fetchers/registry.hh"
|
|
|
|
#include "fetchers/regex.hh"
|
2019-02-12 19:23:11 +02:00
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
#if 0
|
2019-05-01 21:23:39 +03:00
|
|
|
// 'dir' path elements cannot start with a '.'. We also reject
|
|
|
|
// potentially dangerous characters like ';'.
|
|
|
|
const static std::string subDirElemRegex = "(?:[a-zA-Z0-9_-]+[a-zA-Z0-9._-]*)";
|
|
|
|
const static std::string subDirRegex = subDirElemRegex + "(?:/" + subDirElemRegex + ")*";
|
2020-01-21 17:27:53 +02:00
|
|
|
#endif
|
2019-05-01 21:23:39 +03:00
|
|
|
|
2019-05-01 21:43:16 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
std::string FlakeRef::to_string() const
|
2019-02-12 19:23:11 +02:00
|
|
|
{
|
2020-01-21 17:27:53 +02:00
|
|
|
return input->to_string();
|
|
|
|
}
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
bool FlakeRef::isDirect() const
|
|
|
|
{
|
|
|
|
return input->isDirect();
|
|
|
|
}
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
bool FlakeRef::isImmutable() const
|
|
|
|
{
|
|
|
|
return input->isImmutable();
|
|
|
|
}
|
2019-05-01 21:23:39 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef)
|
|
|
|
{
|
|
|
|
str << flakeRef.to_string();
|
|
|
|
return str;
|
|
|
|
}
|
2019-05-31 20:01:11 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
bool FlakeRef::operator==(const FlakeRef & other) const
|
|
|
|
{
|
|
|
|
return *input == *other.input && subdir == other.subdir;
|
|
|
|
}
|
2019-05-31 20:01:11 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
FlakeRef FlakeRef::resolve(ref<Store> store) const
|
|
|
|
{
|
|
|
|
return FlakeRef(lookupInRegistries(store, input), subdir);
|
|
|
|
}
|
2019-05-31 20:01:11 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
FlakeRef parseFlakeRef(
|
|
|
|
const std::string & url, const std::optional<Path> & baseDir)
|
|
|
|
{
|
|
|
|
auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir);
|
|
|
|
if (fragment != "")
|
|
|
|
throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url);
|
|
|
|
return flakeRef;
|
|
|
|
}
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
std::optional<FlakeRef> maybeParseFlakeRef(
|
|
|
|
const std::string & url, const std::optional<Path> & baseDir)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
return parseFlakeRef(url, baseDir);
|
|
|
|
} catch (Error &) {
|
|
|
|
return {};
|
2019-02-12 19:23:11 +02:00
|
|
|
}
|
2020-01-21 17:27:53 +02:00
|
|
|
}
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|
|
|
const std::string & url, const std::optional<Path> & baseDir)
|
|
|
|
{
|
|
|
|
using namespace fetchers;
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
static std::regex pathUrlRegex(
|
|
|
|
"(" + pathRegex + "/?)"
|
|
|
|
+ "(?:\\?(" + queryRegex + "))?"
|
|
|
|
+ "(?:#(" + queryRegex + "))?",
|
|
|
|
std::regex::ECMAScript);
|
2019-04-08 23:46:25 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
static std::regex flakeRegex(
|
|
|
|
"((" + flakeId + ")(?:/(?:" + refAndOrRevRegex + "))?)"
|
|
|
|
+ "(?:#(" + queryRegex + "))?",
|
|
|
|
std::regex::ECMAScript);
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
std::smatch match;
|
2019-05-08 00:06:15 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
|
|
|
|
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
|
|
|
|
|
|
|
|
if (std::regex_match(url, match, flakeRegex)) {
|
|
|
|
auto parsedURL = ParsedURL{
|
|
|
|
.url = url,
|
|
|
|
.base = "flake:" + std::string(match[1]),
|
|
|
|
.scheme = "flake",
|
|
|
|
.authority = "",
|
|
|
|
.path = match[1],
|
|
|
|
.fragment = percentDecode(std::string(match[6]))
|
2019-05-08 00:06:15 +03:00
|
|
|
};
|
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
return std::make_pair(
|
|
|
|
FlakeRef(inputFromURL(parsedURL), ""),
|
|
|
|
parsedURL.fragment);
|
2019-05-08 00:06:15 +03:00
|
|
|
}
|
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
/* Check if 'url' is a path (either absolute or relative to
|
|
|
|
'baseDir'). If so, search upward to the root of the repo
|
|
|
|
(i.e. the directory containing .git). */
|
|
|
|
|
|
|
|
else if (std::regex_match(url, match, pathUrlRegex)) {
|
|
|
|
std::string path = match[1];
|
|
|
|
if (!baseDir && !hasPrefix(path, "/"))
|
|
|
|
throw BadURL("flake reference '%s' is not an absolute path", url);
|
|
|
|
path = absPath(path, baseDir, true);
|
|
|
|
|
2020-01-24 14:01:07 +02:00
|
|
|
if (!S_ISDIR(lstat(path).st_mode))
|
|
|
|
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
|
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
auto flakeRoot = path;
|
|
|
|
std::string subdir;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
if (pathExists(flakeRoot + "/.git")) break;
|
|
|
|
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
|
|
|
|
flakeRoot = dirOf(flakeRoot);
|
|
|
|
if (flakeRoot == "/")
|
|
|
|
throw BadURL("path '%s' is not a flake (because it does not reference a Git repository)", path);
|
|
|
|
}
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
auto base = std::string("git+file://") + flakeRoot;
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
auto parsedURL = ParsedURL{
|
|
|
|
.url = base, // FIXME
|
|
|
|
.base = base,
|
|
|
|
.scheme = "git+file",
|
|
|
|
.authority = "",
|
|
|
|
.path = flakeRoot,
|
|
|
|
.query = decodeQuery(match[2]),
|
|
|
|
.fragment = percentDecode(std::string(match[3]))
|
|
|
|
};
|
2019-02-12 19:23:11 +02:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
if (subdir != "") {
|
|
|
|
if (parsedURL.query.count("subdir"))
|
|
|
|
throw Error("flake URL '%s' has an inconsistent 'subdir' parameter", url);
|
|
|
|
parsedURL.query.insert_or_assign("subdir", subdir);
|
2019-05-08 00:06:15 +03:00
|
|
|
}
|
2019-04-06 21:45:35 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
return std::make_pair(
|
|
|
|
FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "subdir").value_or("")),
|
|
|
|
parsedURL.fragment);
|
2019-05-08 00:06:15 +03:00
|
|
|
}
|
2019-05-01 21:23:39 +03:00
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
else {
|
|
|
|
auto parsedURL = parseURL(url);
|
|
|
|
return std::make_pair(
|
|
|
|
FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "subdir").value_or("")),
|
|
|
|
parsedURL.fragment);
|
|
|
|
}
|
2019-09-19 00:59:45 +03:00
|
|
|
}
|
|
|
|
|
2020-01-21 17:27:53 +02:00
|
|
|
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
|
|
|
const std::string & url, const std::optional<Path> & baseDir)
|
2019-05-31 21:53:23 +03:00
|
|
|
{
|
|
|
|
try {
|
2020-01-21 17:27:53 +02:00
|
|
|
return parseFlakeRefWithFragment(url, baseDir);
|
|
|
|
} catch (Error & e) {
|
|
|
|
printError("FOO: %s", e.what());
|
2019-05-31 21:53:23 +03:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-12 19:23:11 +02:00
|
|
|
}
|