Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
E.g. $ nix eval '(fetchMercurial https://www.mercurial-scm.org/repo/hello)' { branch = "default"; outPath = "/nix/store/alvb9y1kfz42bjishqmyy3pphnrh1pfa-source"; rev = "82e55d328c8ca4ee16520036c0aaace03a5beb65"; revCount = 1; shortRev = "82e55d328c8c"; } $ nix eval '(fetchMercurial { url = https://www.mercurial-scm.org/repo/hello; rev = "0a04b987be5ae354b710cefeba0e2d9de7ad41a9"; })' { branch = "default"; outPath = "/nix/store/alvb9y1kfz42bjishqmyy3pphnrh1pfa-source"; rev = "0a04b987be5ae354b710cefeba0e2d9de7ad41a9"; revCount = 0; shortRev = "0a04b987be5a"; } $ nix eval '(fetchMercurial /tmp/unclean-hg-tree)' { branch = "default"; outPath = "/nix/store/cm750cdw1x8wfpm3jq7mz09r30l9r024-source"; rev = "0000000000000000000000000000000000000000"; revCount = 0; shortRev = "000000000000"; }
- Loading branch information
Showing
7 changed files
with
269 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
#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; | ||
}; | ||
|
||
HgInfo exportMercurial(ref<Store> store, const std::string & uri, | ||
std::string rev, const std::string & name) | ||
{ | ||
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)); | ||
auto st = lstat(p); | ||
std::string file(p, uri.size() + 1); | ||
if (file == ".hg") return false; | ||
// FIXME: filter out directories with no tracked files. | ||
if (S_ISDIR(st.st_mode)) return true; | ||
return files.count(file); | ||
}; | ||
|
||
hgInfo.storePath = 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. FIXME: don't do this if "rev" is a hash and we | ||
fetched it previously */ | ||
time_t now = time(0); | ||
struct stat st; | ||
if (stat(stampFile.c_str(), &st) != 0 || | ||
st.st_mtime < now - settings.tarballTtl) | ||
{ | ||
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", uri)); | ||
|
||
if (pathExists(cacheDir)) { | ||
runProgram("hg", true, { "pull", "-R", cacheDir, "--", uri }); | ||
} 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(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->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 'fetchGit', 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); | ||
|
||
if (!isUri(url)) url = absPath(url); | ||
|
||
// 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(); | ||
} | ||
|
||
static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
source common.sh | ||
|
||
if [[ -z $(type -p hg) ]]; then | ||
echo "Mercurial not installed; skipping Mercurial tests" | ||
exit 0 | ||
fi | ||
|
||
clearStore | ||
|
||
repo=$TEST_ROOT/hg | ||
|
||
rm -rfv $repo ${repo}-tmp $TEST_HOME/.cache/nix/hg | ||
|
||
hg init $repo | ||
echo '[ui]' >> $repo/.hg/hgrc | ||
echo 'username = Foobar <foobar@example.org>' >> $repo/.hg/hgrc | ||
|
||
echo utrecht > $repo/hello | ||
hg add --cwd $repo hello | ||
hg commit --cwd $repo -m 'Bla1' | ||
rev1=$(hg log --cwd $repo -r tip --template '{node}') | ||
|
||
echo world > $repo/hello | ||
hg commit --cwd $repo -m 'Bla2' | ||
rev2=$(hg log --cwd $repo -r tip --template '{node}') | ||
|
||
hg log --cwd $repo | ||
|
||
hg log --cwd $repo -r tip --template '{node}\n' | ||
|
||
path=$(nix eval --raw "(builtins.fetchMercurial file://$repo).outPath") | ||
[[ $(cat $path/hello) = world ]] | ||
|
||
# Fetch again. This should be cached. | ||
mv $repo ${repo}-tmp | ||
path2=$(nix eval --raw "(builtins.fetchMercurial file://$repo).outPath") | ||
[[ $path = $path2 ]] | ||
|
||
[[ $(nix eval --raw "(builtins.fetchMercurial file://$repo).branch") = default ]] | ||
[[ $(nix eval "(builtins.fetchMercurial file://$repo).revCount") = 1 ]] | ||
[[ $(nix eval --raw "(builtins.fetchMercurial file://$repo).rev") = $rev2 ]] | ||
|
||
# But with TTL 0, it should fail. | ||
(! nix eval --tarball-ttl 0 --raw "(builtins.fetchMercurial file://$repo)") | ||
|
||
mv ${repo}-tmp $repo | ||
|
||
# Using a clean working tree should produce the same result. | ||
path2=$(nix eval --raw "(builtins.fetchMercurial $repo).outPath") | ||
[[ $path = $path2 ]] | ||
|
||
# Using an unclean tree should yield the tracked but uncommitted changes. | ||
echo foo > $repo/foo | ||
echo bar > $repo/bar | ||
hg add --cwd $repo foo | ||
hg rm --cwd $repo hello | ||
|
||
path2=$(nix eval --raw "(builtins.fetchMercurial $repo).outPath") | ||
[ ! -e $path2/hello ] | ||
[ ! -e $path2/bar ] | ||
[[ $(cat $path2/foo) = foo ]] | ||
|
||
[[ $(nix eval --raw "(builtins.fetchMercurial $repo).rev") = 0000000000000000000000000000000000000000 ]] | ||
|
||
# ... unless we're using an explicit rev. | ||
path3=$(nix eval --raw "(builtins.fetchMercurial { url = $repo; rev = \"default\"; }).outPath") | ||
[[ $path = $path3 ]] | ||
|
||
# Committing should not affect the store path. | ||
hg commit --cwd $repo -m 'Bla3' | ||
|
||
path4=$(nix eval --tarball-ttl 0 --raw "(builtins.fetchMercurial file://$repo).outPath") | ||
[[ $path2 = $path4 ]] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters