forked from lix-project/lix
bbe97dff8b
Most functions now take a StorePath argument rather than a Path (which is just an alias for std::string). The StorePath constructor ensures that the path is syntactically correct (i.e. it looks like <store-dir>/<base32-hash>-<name>). Similarly, functions like buildPaths() now take a StorePathWithOutputs, rather than abusing Path by adding a '!<outputs>' suffix. Note that the StorePath type is implemented in Rust. This involves some hackery to allow Rust values to be used directly in C++, via a helper type whose destructor calls the Rust type's drop() function. The main issue is the dynamic nature of C++ move semantics: after we have moved a Rust value, we should not call the drop function on the original value. So when we move a value, we set the original value to bitwise zero, and the destructor only calls drop() if the value is not bitwise zero. This should be sufficient for most types. Also lots of minor cleanups to the C++ API to make it more modern (e.g. using std::optional and std::string_view in some places).
219 lines
7.3 KiB
C++
219 lines
7.3 KiB
C++
#include "primops.hh"
|
||
#include "eval-inline.hh"
|
||
#include "download.hh"
|
||
#include "store-api.hh"
|
||
#include "pathlocks.hh"
|
||
|
||
#include <sys/time.h>
|
||
|
||
#include <regex>
|
||
|
||
#include <nlohmann/json.hpp>
|
||
|
||
using namespace std::string_literals;
|
||
|
||
namespace nix {
|
||
|
||
struct HgInfo
|
||
{
|
||
Path storePath;
|
||
std::string branch;
|
||
std::string rev;
|
||
uint64_t revCount = 0;
|
||
};
|
||
|
||
std::regex commitHashRegex("^[0-9a-fA-F]{40}$");
|
||
|
||
HgInfo exportMercurial(ref<Store> store, const std::string & uri,
|
||
std::string rev, const std::string & name)
|
||
{
|
||
if (evalSettings.pureEval && rev == "")
|
||
throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
|
||
|
||
if (rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.hg")) {
|
||
|
||
bool clean = runProgram("hg", true, { "status", "-R", uri, "--modified", "--added", "--removed" }) == "";
|
||
|
||
if (!clean) {
|
||
|
||
/* This is an unclean working tree. So copy all tracked
|
||
files. */
|
||
|
||
printTalkative("copying unclean Mercurial working tree '%s'", uri);
|
||
|
||
HgInfo hgInfo;
|
||
hgInfo.rev = "0000000000000000000000000000000000000000";
|
||
hgInfo.branch = chomp(runProgram("hg", true, { "branch", "-R", uri }));
|
||
|
||
auto files = tokenizeString<std::set<std::string>>(
|
||
runProgram("hg", true, { "status", "-R", uri, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
|
||
|
||
PathFilter filter = [&](const Path & p) -> bool {
|
||
assert(hasPrefix(p, uri));
|
||
std::string file(p, uri.size() + 1);
|
||
|
||
auto st = lstat(p);
|
||
|
||
if (S_ISDIR(st.st_mode)) {
|
||
auto prefix = file + "/";
|
||
auto i = files.lower_bound(prefix);
|
||
return i != files.end() && hasPrefix(*i, prefix);
|
||
}
|
||
|
||
return files.count(file);
|
||
};
|
||
|
||
hgInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, htSHA256, filter));
|
||
|
||
return hgInfo;
|
||
}
|
||
}
|
||
|
||
if (rev == "") rev = "default";
|
||
|
||
Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(htSHA256, uri).to_string(Base32, false));
|
||
|
||
Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, hashString(htSHA512, rev).to_string(Base32, false));
|
||
|
||
/* If we haven't pulled this repo less than ‘tarball-ttl’ seconds,
|
||
do so now. */
|
||
time_t now = time(0);
|
||
struct stat st;
|
||
if (stat(stampFile.c_str(), &st) != 0 ||
|
||
(uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now)
|
||
{
|
||
/* Except that if this is a commit hash that we already have,
|
||
we don't have to pull again. */
|
||
if (!(std::regex_match(rev, commitHashRegex)
|
||
&& pathExists(cacheDir)
|
||
&& runProgram(
|
||
RunOptions("hg", { "log", "-R", cacheDir, "-r", rev, "--template", "1" })
|
||
.killStderr(true)).second == "1"))
|
||
{
|
||
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", uri));
|
||
|
||
if (pathExists(cacheDir)) {
|
||
try {
|
||
runProgram("hg", true, { "pull", "-R", cacheDir, "--", uri });
|
||
}
|
||
catch (ExecError & e) {
|
||
string transJournal = cacheDir + "/.hg/store/journal";
|
||
/* hg throws "abandoned transaction" error only if this file exists */
|
||
if (pathExists(transJournal)) {
|
||
runProgram("hg", true, { "recover", "-R", cacheDir });
|
||
runProgram("hg", true, { "pull", "-R", cacheDir, "--", uri });
|
||
} else {
|
||
throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status)));
|
||
}
|
||
}
|
||
} else {
|
||
createDirs(dirOf(cacheDir));
|
||
runProgram("hg", true, { "clone", "--noupdate", "--", uri, cacheDir });
|
||
}
|
||
}
|
||
|
||
writeFile(stampFile, "");
|
||
}
|
||
|
||
auto tokens = tokenizeString<std::vector<std::string>>(
|
||
runProgram("hg", true, { "log", "-R", cacheDir, "-r", rev, "--template", "{node} {rev} {branch}" }));
|
||
assert(tokens.size() == 3);
|
||
|
||
HgInfo hgInfo;
|
||
hgInfo.rev = tokens[0];
|
||
hgInfo.revCount = std::stoull(tokens[1]);
|
||
hgInfo.branch = tokens[2];
|
||
|
||
std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + hgInfo.rev).to_string(Base32, false);
|
||
Path storeLink = fmt("%s/.hg/%s.link", cacheDir, storeLinkName);
|
||
|
||
try {
|
||
auto json = nlohmann::json::parse(readFile(storeLink));
|
||
|
||
assert(json["name"] == name && json["rev"] == hgInfo.rev);
|
||
|
||
hgInfo.storePath = json["storePath"];
|
||
|
||
if (store->isValidPath(store->parseStorePath(hgInfo.storePath))) {
|
||
printTalkative("using cached Mercurial store path '%s'", hgInfo.storePath);
|
||
return hgInfo;
|
||
}
|
||
|
||
} catch (SysError & e) {
|
||
if (e.errNo != ENOENT) throw;
|
||
}
|
||
|
||
Path tmpDir = createTempDir();
|
||
AutoDelete delTmpDir(tmpDir, true);
|
||
|
||
runProgram("hg", true, { "archive", "-R", cacheDir, "-r", rev, tmpDir });
|
||
|
||
deletePath(tmpDir + "/.hg_archival.txt");
|
||
|
||
hgInfo.storePath = store->printStorePath(store->addToStore(name, tmpDir));
|
||
|
||
nlohmann::json json;
|
||
json["storePath"] = hgInfo.storePath;
|
||
json["uri"] = uri;
|
||
json["name"] = name;
|
||
json["branch"] = hgInfo.branch;
|
||
json["rev"] = hgInfo.rev;
|
||
json["revCount"] = hgInfo.revCount;
|
||
|
||
writeFile(storeLink, json.dump());
|
||
|
||
return hgInfo;
|
||
}
|
||
|
||
static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||
{
|
||
std::string url;
|
||
std::string rev;
|
||
std::string name = "source";
|
||
PathSet context;
|
||
|
||
state.forceValue(*args[0]);
|
||
|
||
if (args[0]->type == tAttrs) {
|
||
|
||
state.forceAttrs(*args[0], pos);
|
||
|
||
for (auto & attr : *args[0]->attrs) {
|
||
string n(attr.name);
|
||
if (n == "url")
|
||
url = state.coerceToString(*attr.pos, *attr.value, context, false, false);
|
||
else if (n == "rev")
|
||
rev = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||
else if (n == "name")
|
||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||
else
|
||
throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", attr.name, *attr.pos);
|
||
}
|
||
|
||
if (url.empty())
|
||
throw EvalError(format("'url' argument required, at %1%") % pos);
|
||
|
||
} else
|
||
url = state.coerceToString(pos, *args[0], context, false, false);
|
||
|
||
// FIXME: git externals probably can be used to bypass the URI
|
||
// whitelist. Ah well.
|
||
state.checkURI(url);
|
||
|
||
auto hgInfo = exportMercurial(state.store, url, rev, name);
|
||
|
||
state.mkAttrs(v, 8);
|
||
mkString(*state.allocAttr(v, state.sOutPath), hgInfo.storePath, PathSet({hgInfo.storePath}));
|
||
mkString(*state.allocAttr(v, state.symbols.create("branch")), hgInfo.branch);
|
||
mkString(*state.allocAttr(v, state.symbols.create("rev")), hgInfo.rev);
|
||
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), std::string(hgInfo.rev, 0, 12));
|
||
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), hgInfo.revCount);
|
||
v.attrs->sort();
|
||
|
||
if (state.allowedPaths)
|
||
state.allowedPaths->insert(state.store->toRealPath(hgInfo.storePath));
|
||
}
|
||
|
||
static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial);
|
||
|
||
}
|