aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/eval.cc4
-rw-r--r--src/libexpr/function-trace.cc4
-rw-r--r--src/libexpr/parser.y2
-rw-r--r--src/libexpr/primops.cc148
-rw-r--r--src/libexpr/primops/fetchGit.cc356
-rw-r--r--src/libexpr/primops/fetchMercurial.cc306
6 files changed, 805 insertions, 15 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 82eb1582e..99c1070ce 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -1669,10 +1669,10 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
else {
auto p = settings.readOnlyMode
? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first
- : store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair);
+ : store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, HashType::SHA256, defaultPathFilter, repair);
dstPath = store->printStorePath(p);
srcToStore.insert_or_assign(path, std::move(p));
- printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, dstPath);
+ printMsg(Verbosity::Chatty, "copied source '%1%' -> '%2%'", path, dstPath);
}
context.insert(dstPath);
diff --git a/src/libexpr/function-trace.cc b/src/libexpr/function-trace.cc
index c6057b384..882da9937 100644
--- a/src/libexpr/function-trace.cc
+++ b/src/libexpr/function-trace.cc
@@ -6,13 +6,13 @@ namespace nix {
FunctionCallTrace::FunctionCallTrace(const Pos & pos) : pos(pos) {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
- printMsg(lvlInfo, "function-trace entered %1% at %2%", pos, ns.count());
+ printMsg(Verbosity::Info, "function-trace entered %1% at %2%", pos, ns.count());
}
FunctionCallTrace::~FunctionCallTrace() {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
- printMsg(lvlInfo, "function-trace exited %1% at %2%", pos, ns.count());
+ printMsg(Verbosity::Info, "function-trace exited %1% at %2%", pos, ns.count());
}
}
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 1993fa6c1..991de24af 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -626,7 +626,7 @@ Expr * EvalState::parseExprFromString(std::string_view s, const Path & basePath)
Expr * EvalState::parseStdin()
{
- //Activity act(*logger, lvlTalkative, format("parsing standard input"));
+ //Activity act(*logger, Verbosity::Talkative, format("parsing standard input"));
return parseExprFromString(drainFD(0), absPath("."));
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index d458ab272..044f3290a 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -718,7 +718,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
if (outputs.size() != 1 || *(outputs.begin()) != "out")
throw Error(format("multiple outputs are not supported in fixed-output derivations, at %1%") % posDrvName);
- HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo);
+ HashType ht = outputHashAlgo.empty() ? HashType::Unknown : parseHashType(outputHashAlgo);
Hash h(*outputHash, ht);
auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName);
@@ -727,7 +727,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
std::move(outPath),
(ingestionMethod == FileIngestionMethod::Recursive ? "r:" : "")
+ printHashType(h.type),
- h.to_string(Base16, false),
+ h.to_string(Base::Base16, false),
});
}
@@ -758,7 +758,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
auto drvPath = writeDerivation(state.store, drv, drvName, state.repair);
auto drvPathS = state.store->printStorePath(drvPath);
- printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
+ printMsg(Verbosity::Chatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
/* Optimisation, but required in read-only mode! because in that
case we don't actually write store derivations, so we can't
@@ -935,13 +935,13 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
{
string type = state.forceStringNoCtx(*args[0], pos);
HashType ht = parseHashType(type);
- if (ht == htUnknown)
+ if (ht == HashType::Unknown)
throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
PathSet context; // discarded
Path p = state.coerceToPath(pos, *args[1], context);
- mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), context);
+ mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base::Base16, false), context);
}
/* Read a directory (without . or ..) */
@@ -1077,8 +1077,8 @@ static void addPath(EvalState & state, const Pos & pos, const string & name, con
Path dstPath;
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
dstPath = state.store->printStorePath(settings.readOnlyMode
- ? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
- : state.store->addToStore(name, path, method, htSHA256, filter, state.repair));
+ ? state.store->computeStorePathForPath(name, path, method, HashType::SHA256, filter).first
+ : state.store->addToStore(name, path, method, HashType::SHA256, filter, state.repair));
if (expectedHash && expectedStorePath != state.store->parseStorePath(dstPath))
throw Error("store path mismatch in (possibly filtered) path added from '%s'", path);
} else
@@ -1126,7 +1126,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
} else if (n == "recursive")
method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos) };
else if (n == "sha256")
- expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+ expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), HashType::SHA256);
else
throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos);
}
@@ -1813,13 +1813,13 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
{
string type = state.forceStringNoCtx(*args[0], pos);
HashType ht = parseHashType(type);
- if (ht == htUnknown)
+ if (ht == HashType::Unknown)
throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
PathSet context; // discarded
string s = state.forceString(*args[1], context, pos);
- mkString(v, hashString(ht, s).to_string(Base16, false), context);
+ mkString(v, hashString(ht, s).to_string(Base::Base16, false), context);
}
@@ -2053,6 +2053,134 @@ static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args
/*************************************************************
+<<<<<<< HEAD
+ * Networking
+ *************************************************************/
+
+
+void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
+ const string & who, bool unpack, const std::string & defaultName)
+{
+ CachedDownloadRequest request("");
+ request.unpack = unpack;
+ request.name = defaultName;
+
+ 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")
+ request.uri = state.forceStringNoCtx(*attr.value, *attr.pos);
+ else if (n == "sha256")
+ request.expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), HashType::SHA256);
+ else if (n == "name")
+ request.name = state.forceStringNoCtx(*attr.value, *attr.pos);
+ else
+ throw EvalError(format("unsupported argument '%1%' to '%2%', at %3%") % attr.name % who % attr.pos);
+ }
+
+ if (request.uri.empty())
+ throw EvalError(format("'url' argument required, at %1%") % pos);
+
+ } else
+ request.uri = state.forceStringNoCtx(*args[0], pos);
+
+ state.checkURI(request.uri);
+
+ if (evalSettings.pureEval && !request.expectedHash)
+ throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who);
+
+ auto res = getDownloader()->downloadCached(state.store, request);
+
+ if (state.allowedPaths)
+ state.allowedPaths->insert(res.path);
+
+ mkString(v, res.storePath, PathSet({res.storePath}));
+}
+
+
+static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ fetch(state, pos, args, v, "fetchurl", false, "");
+}
+
+
+static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ fetch(state, pos, args, v, "fetchTarball", true, "source");
+}
+
+
+/*************************************************************
+||||||| merged common ancestors
+ * Networking
+ *************************************************************/
+
+
+void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
+ const string & who, bool unpack, const std::string & defaultName)
+{
+ CachedDownloadRequest request("");
+ request.unpack = unpack;
+ request.name = defaultName;
+
+ 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")
+ request.uri = state.forceStringNoCtx(*attr.value, *attr.pos);
+ else if (n == "sha256")
+ request.expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+ else if (n == "name")
+ request.name = state.forceStringNoCtx(*attr.value, *attr.pos);
+ else
+ throw EvalError(format("unsupported argument '%1%' to '%2%', at %3%") % attr.name % who % attr.pos);
+ }
+
+ if (request.uri.empty())
+ throw EvalError(format("'url' argument required, at %1%") % pos);
+
+ } else
+ request.uri = state.forceStringNoCtx(*args[0], pos);
+
+ state.checkURI(request.uri);
+
+ if (evalSettings.pureEval && !request.expectedHash)
+ throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who);
+
+ auto res = getDownloader()->downloadCached(state.store, request);
+
+ if (state.allowedPaths)
+ state.allowedPaths->insert(res.path);
+
+ mkString(v, res.storePath, PathSet({res.storePath}));
+}
+
+
+static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ fetch(state, pos, args, v, "fetchurl", false, "");
+}
+
+
+static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ fetch(state, pos, args, v, "fetchTarball", true, "source");
+}
+
+
+/*************************************************************
+=======
+>>>>>>> f60ce4fa207a210e23a1142d3a8ead611526e6e1
* Primop registration
*************************************************************/
diff --git a/src/libexpr/primops/fetchGit.cc b/src/libexpr/primops/fetchGit.cc
index 1a8798fcc..1c8df145a 100644
--- a/src/libexpr/primops/fetchGit.cc
+++ b/src/libexpr/primops/fetchGit.cc
@@ -7,6 +7,362 @@
namespace nix {
+<<<<<<< HEAD
+struct GitInfo
+{
+ Path storePath;
+ std::string rev;
+ std::string shortRev;
+ uint64_t revCount = 0;
+};
+
+std::regex revRegex("^[0-9a-fA-F]{40}$");
+
+GitInfo exportGit(ref<Store> store, const std::string & uri,
+ std::optional<std::string> ref, std::string rev,
+ const std::string & name)
+{
+ if (evalSettings.pureEval && rev == "")
+ throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
+
+ if (!ref && rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.git")) {
+
+ bool clean = true;
+
+ try {
+ runProgram("git", true, { "-C", uri, "diff-index", "--quiet", "HEAD", "--" });
+ } catch (ExecError & e) {
+ if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw;
+ clean = false;
+ }
+
+ if (!clean) {
+
+ /* This is an unclean working tree. So copy all tracked files. */
+ GitInfo gitInfo;
+ gitInfo.rev = "0000000000000000000000000000000000000000";
+ gitInfo.shortRev = std::string(gitInfo.rev, 0, 7);
+
+ auto files = tokenizeString<std::set<std::string>>(
+ runProgram("git", true, { "-C", uri, "ls-files", "-z" }), "\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);
+ };
+
+ gitInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, HashType::SHA256, filter));
+
+ return gitInfo;
+ }
+
+ // clean working tree, but no ref or rev specified. Use 'HEAD'.
+ rev = chomp(runProgram("git", true, { "-C", uri, "rev-parse", "HEAD" }));
+ ref = "HEAD"s;
+ }
+
+ if (!ref) ref = "HEAD"s;
+
+ if (rev != "" && !std::regex_match(rev, revRegex))
+ throw Error("invalid Git revision '%s'", rev);
+
+ deletePath(getCacheDir() + "/nix/git");
+
+ Path cacheDir = getCacheDir() + "/nix/gitv2/" + hashString(HashType::SHA256, uri).to_string(Base::Base32, false);
+
+ if (!pathExists(cacheDir)) {
+ createDirs(dirOf(cacheDir));
+ runProgram("git", true, { "init", "--bare", cacheDir });
+ }
+
+ Path localRefFile;
+ if (ref->compare(0, 5, "refs/") == 0)
+ localRefFile = cacheDir + "/" + *ref;
+ else
+ localRefFile = cacheDir + "/refs/heads/" + *ref;
+
+ bool doFetch;
+ time_t now = time(0);
+ /* If a rev was specified, we need to fetch if it's not in the
+ repo. */
+ if (rev != "") {
+ try {
+ runProgram("git", true, { "-C", cacheDir, "cat-file", "-e", rev });
+ doFetch = false;
+ } catch (ExecError & e) {
+ if (WIFEXITED(e.status)) {
+ doFetch = true;
+ } else {
+ throw;
+ }
+ }
+ } else {
+ /* 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 ||
+ (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now;
+ }
+ if (doFetch)
+ {
+ Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("fetching Git repository '%s'", uri));
+
+ // FIXME: git stderr messes up our progress indicator, so
+ // we're using --quiet for now. Should process its stderr.
+ runProgram("git", true, { "-C", cacheDir, "fetch", "--quiet", "--force", "--", uri, fmt("%s:%s", *ref, *ref) });
+
+ struct timeval times[2];
+ times[0].tv_sec = now;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = now;
+ times[1].tv_usec = 0;
+
+ utimes(localRefFile.c_str(), times);
+ }
+
+ // FIXME: check whether rev is an ancestor of ref.
+ GitInfo gitInfo;
+ gitInfo.rev = rev != "" ? rev : chomp(readFile(localRefFile));
+ gitInfo.shortRev = std::string(gitInfo.rev, 0, 7);
+
+ printTalkative("using revision %s of repo '%s'", gitInfo.rev, uri);
+
+ std::string storeLinkName = hashString(HashType::SHA512, name + std::string("\0"s) + gitInfo.rev).to_string(Base::Base32, false);
+ Path storeLink = cacheDir + "/" + storeLinkName + ".link";
+ PathLocks storeLinkLock({storeLink}, fmt("waiting for lock on '%1%'...", storeLink)); // FIXME: broken
+
+ try {
+ auto json = nlohmann::json::parse(readFile(storeLink));
+
+ assert(json["name"] == name && json["rev"] == gitInfo.rev);
+
+ gitInfo.storePath = json["storePath"];
+
+ if (store->isValidPath(store->parseStorePath(gitInfo.storePath))) {
+ gitInfo.revCount = json["revCount"];
+ return gitInfo;
+ }
+
+ } catch (SysError & e) {
+ if (e.errNo != ENOENT) throw;
+ }
+
+ auto source = sinkToSource([&](Sink & sink) {
+ RunOptions gitOptions("git", { "-C", cacheDir, "archive", gitInfo.rev });
+ gitOptions.standardOut = &sink;
+ runProgram2(gitOptions);
+ });
+
+ Path tmpDir = createTempDir();
+ AutoDelete delTmpDir(tmpDir, true);
+
+ unpackTarfile(*source, tmpDir);
+
+ gitInfo.storePath = store->printStorePath(store->addToStore(name, tmpDir));
+
+ gitInfo.revCount = std::stoull(runProgram("git", true, { "-C", cacheDir, "rev-list", "--count", gitInfo.rev }));
+
+ nlohmann::json json;
+ json["storePath"] = gitInfo.storePath;
+ json["uri"] = uri;
+ json["name"] = name;
+ json["rev"] = gitInfo.rev;
+ json["revCount"] = gitInfo.revCount;
+
+ writeFile(storeLink, json.dump());
+
+ return gitInfo;
+}
+
+||||||| merged common ancestors
+struct GitInfo
+{
+ Path storePath;
+ std::string rev;
+ std::string shortRev;
+ uint64_t revCount = 0;
+};
+
+std::regex revRegex("^[0-9a-fA-F]{40}$");
+
+GitInfo exportGit(ref<Store> store, const std::string & uri,
+ std::optional<std::string> ref, std::string rev,
+ const std::string & name)
+{
+ if (evalSettings.pureEval && rev == "")
+ throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
+
+ if (!ref && rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.git")) {
+
+ bool clean = true;
+
+ try {
+ runProgram("git", true, { "-C", uri, "diff-index", "--quiet", "HEAD", "--" });
+ } catch (ExecError & e) {
+ if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw;
+ clean = false;
+ }
+
+ if (!clean) {
+
+ /* This is an unclean working tree. So copy all tracked files. */
+ GitInfo gitInfo;
+ gitInfo.rev = "0000000000000000000000000000000000000000";
+ gitInfo.shortRev = std::string(gitInfo.rev, 0, 7);
+
+ auto files = tokenizeString<std::set<std::string>>(
+ runProgram("git", true, { "-C", uri, "ls-files", "-z" }), "\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);
+ };
+
+ gitInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, htSHA256, filter));
+
+ return gitInfo;
+ }
+
+ // clean working tree, but no ref or rev specified. Use 'HEAD'.
+ rev = chomp(runProgram("git", true, { "-C", uri, "rev-parse", "HEAD" }));
+ ref = "HEAD"s;
+ }
+
+ if (!ref) ref = "HEAD"s;
+
+ if (rev != "" && !std::regex_match(rev, revRegex))
+ throw Error("invalid Git revision '%s'", rev);
+
+ deletePath(getCacheDir() + "/nix/git");
+
+ Path cacheDir = getCacheDir() + "/nix/gitv2/" + hashString(htSHA256, uri).to_string(Base32, false);
+
+ if (!pathExists(cacheDir)) {
+ createDirs(dirOf(cacheDir));
+ runProgram("git", true, { "init", "--bare", cacheDir });
+ }
+
+ Path localRefFile;
+ if (ref->compare(0, 5, "refs/") == 0)
+ localRefFile = cacheDir + "/" + *ref;
+ else
+ localRefFile = cacheDir + "/refs/heads/" + *ref;
+
+ bool doFetch;
+ time_t now = time(0);
+ /* If a rev was specified, we need to fetch if it's not in the
+ repo. */
+ if (rev != "") {
+ try {
+ runProgram("git", true, { "-C", cacheDir, "cat-file", "-e", rev });
+ doFetch = false;
+ } catch (ExecError & e) {
+ if (WIFEXITED(e.status)) {
+ doFetch = true;
+ } else {
+ throw;
+ }
+ }
+ } else {
+ /* 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 ||
+ (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now;
+ }
+ if (doFetch)
+ {
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Git repository '%s'", uri));
+
+ // FIXME: git stderr messes up our progress indicator, so
+ // we're using --quiet for now. Should process its stderr.
+ runProgram("git", true, { "-C", cacheDir, "fetch", "--quiet", "--force", "--", uri, fmt("%s:%s", *ref, *ref) });
+
+ struct timeval times[2];
+ times[0].tv_sec = now;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = now;
+ times[1].tv_usec = 0;
+
+ utimes(localRefFile.c_str(), times);
+ }
+
+ // FIXME: check whether rev is an ancestor of ref.
+ GitInfo gitInfo;
+ gitInfo.rev = rev != "" ? rev : chomp(readFile(localRefFile));
+ gitInfo.shortRev = std::string(gitInfo.rev, 0, 7);
+
+ printTalkative("using revision %s of repo '%s'", gitInfo.rev, uri);
+
+ std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + gitInfo.rev).to_string(Base32, false);
+ Path storeLink = cacheDir + "/" + storeLinkName + ".link";
+ PathLocks storeLinkLock({storeLink}, fmt("waiting for lock on '%1%'...", storeLink)); // FIXME: broken
+
+ try {
+ auto json = nlohmann::json::parse(readFile(storeLink));
+
+ assert(json["name"] == name && json["rev"] == gitInfo.rev);
+
+ gitInfo.storePath = json["storePath"];
+
+ if (store->isValidPath(store->parseStorePath(gitInfo.storePath))) {
+ gitInfo.revCount = json["revCount"];
+ return gitInfo;
+ }
+
+ } catch (SysError & e) {
+ if (e.errNo != ENOENT) throw;
+ }
+
+ auto source = sinkToSource([&](Sink & sink) {
+ RunOptions gitOptions("git", { "-C", cacheDir, "archive", gitInfo.rev });
+ gitOptions.standardOut = &sink;
+ runProgram2(gitOptions);
+ });
+
+ Path tmpDir = createTempDir();
+ AutoDelete delTmpDir(tmpDir, true);
+
+ unpackTarfile(*source, tmpDir);
+
+ gitInfo.storePath = store->printStorePath(store->addToStore(name, tmpDir));
+
+ gitInfo.revCount = std::stoull(runProgram("git", true, { "-C", cacheDir, "rev-list", "--count", gitInfo.rev }));
+
+ nlohmann::json json;
+ json["storePath"] = gitInfo.storePath;
+ json["uri"] = uri;
+ json["name"] = name;
+ json["rev"] = gitInfo.rev;
+ json["revCount"] = gitInfo.revCount;
+
+ writeFile(storeLink, json.dump());
+
+ return gitInfo;
+}
+
+=======
+>>>>>>> f60ce4fa207a210e23a1142d3a8ead611526e6e1
static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
std::string url;
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index 0a1ba49d5..b6c5c74c4 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -8,6 +8,312 @@
namespace nix {
+<<<<<<< HEAD
+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, HashType::SHA256, filter));
+
+ return hgInfo;
+ }
+ }
+
+ if (rev == "") rev = "default";
+
+ Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(HashType::SHA256, uri).to_string(Base::Base32, false));
+
+ Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, hashString(HashType::SHA512, rev).to_string(Base::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, Verbosity::Talkative, ActivityType::Unknown, 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(HashType::SHA512, name + std::string("\0"s) + hgInfo.rev).to_string(Base::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;
+}
+
+||||||| merged common ancestors
+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;
+}
+
+=======
+>>>>>>> f60ce4fa207a210e23a1142d3a8ead611526e6e1
static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
std::string url;