fetchMercurial: set HGPLAIN when invoking hg

Without setting HGPLAIN, the user's environment leaks into
hg invocations, which means that the output may not be in the
expected format.

HGPLAIN is the Mercurial-recommended solution for this in that
it's intended for uses by scripts and programs which are looking
to parse Mercurial's output in a consistent manner.
This commit is contained in:
Luke Granger-Brown 2020-11-23 16:12:33 +00:00
parent 1973669e86
commit 226116f482
2 changed files with 45 additions and 12 deletions

View file

@ -11,6 +11,36 @@ using namespace std::string_literals;
namespace nix::fetchers { namespace nix::fetchers {
namespace {
RunOptions hgOptions(const Strings & args) {
RunOptions opts("hg", args);
opts.searchPath = true;
auto env = getEnv();
// Set HGPLAIN: this means we get consistent output from hg and avoids leakage from a user or system .hgrc.
env["HGPLAIN"] = "";
opts.environment = env;
return opts;
}
// runProgram wrapper that uses hgOptions instead of stock RunOptions.
string runHg(const Strings & args, const std::optional<std::string> & input = {})
{
RunOptions opts = hgOptions(args);
opts.input = input;
auto res = runProgram(opts);
if (!statusOk(res.first))
throw ExecError(res.first, fmt("hg %1%", statusToString(res.first)));
return res.second;
}
}
struct MercurialInputScheme : InputScheme struct MercurialInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) override
@ -100,11 +130,11 @@ struct MercurialInputScheme : InputScheme
assert(sourcePath); assert(sourcePath);
// FIXME: shut up if file is already tracked. // FIXME: shut up if file is already tracked.
runProgram("hg", true, runHg(
{ "add", *sourcePath + "/" + std::string(file) }); { "add", *sourcePath + "/" + std::string(file) });
if (commitMsg) if (commitMsg)
runProgram("hg", true, runHg(
{ "commit", *sourcePath + "/" + std::string(file), "-m", *commitMsg }); { "commit", *sourcePath + "/" + std::string(file), "-m", *commitMsg });
} }
@ -130,7 +160,7 @@ struct MercurialInputScheme : InputScheme
if (!input.getRef() && !input.getRev() && isLocal && pathExists(actualUrl + "/.hg")) { if (!input.getRef() && !input.getRev() && isLocal && pathExists(actualUrl + "/.hg")) {
bool clean = runProgram("hg", true, { "status", "-R", actualUrl, "--modified", "--added", "--removed" }) == ""; bool clean = runHg({ "status", "-R", actualUrl, "--modified", "--added", "--removed" }) == "";
if (!clean) { if (!clean) {
@ -143,10 +173,10 @@ struct MercurialInputScheme : InputScheme
if (settings.warnDirty) if (settings.warnDirty)
warn("Mercurial tree '%s' is unclean", actualUrl); warn("Mercurial tree '%s' is unclean", actualUrl);
input.attrs.insert_or_assign("ref", chomp(runProgram("hg", true, { "branch", "-R", actualUrl }))); input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl })));
auto files = tokenizeString<std::set<std::string>>( auto files = tokenizeString<std::set<std::string>>(
runProgram("hg", true, { "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s); runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
PathFilter filter = [&](const Path & p) -> bool { PathFilter filter = [&](const Path & p) -> bool {
assert(hasPrefix(p, actualUrl)); assert(hasPrefix(p, actualUrl));
@ -224,33 +254,33 @@ struct MercurialInputScheme : InputScheme
if (!(input.getRev() if (!(input.getRev()
&& pathExists(cacheDir) && pathExists(cacheDir)
&& runProgram( && runProgram(
RunOptions("hg", { "log", "-R", cacheDir, "-r", input.getRev()->gitRev(), "--template", "1" }) hgOptions({ "log", "-R", cacheDir, "-r", input.getRev()->gitRev(), "--template", "1" })
.killStderr(true)).second == "1")) .killStderr(true)).second == "1"))
{ {
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", actualUrl)); Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", actualUrl));
if (pathExists(cacheDir)) { if (pathExists(cacheDir)) {
try { try {
runProgram("hg", true, { "pull", "-R", cacheDir, "--", actualUrl }); runHg({ "pull", "-R", cacheDir, "--", actualUrl });
} }
catch (ExecError & e) { catch (ExecError & e) {
string transJournal = cacheDir + "/.hg/store/journal"; string transJournal = cacheDir + "/.hg/store/journal";
/* hg throws "abandoned transaction" error only if this file exists */ /* hg throws "abandoned transaction" error only if this file exists */
if (pathExists(transJournal)) { if (pathExists(transJournal)) {
runProgram("hg", true, { "recover", "-R", cacheDir }); runHg({ "recover", "-R", cacheDir });
runProgram("hg", true, { "pull", "-R", cacheDir, "--", actualUrl }); runHg({ "pull", "-R", cacheDir, "--", actualUrl });
} else { } else {
throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status))); throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status)));
} }
} }
} else { } else {
createDirs(dirOf(cacheDir)); createDirs(dirOf(cacheDir));
runProgram("hg", true, { "clone", "--noupdate", "--", actualUrl, cacheDir }); runHg({ "clone", "--noupdate", "--", actualUrl, cacheDir });
} }
} }
auto tokens = tokenizeString<std::vector<std::string>>( auto tokens = tokenizeString<std::vector<std::string>>(
runProgram("hg", true, { "log", "-R", cacheDir, "-r", revOrRef, "--template", "{node} {rev} {branch}" })); runHg({ "log", "-R", cacheDir, "-r", revOrRef, "--template", "{node} {rev} {branch}" }));
assert(tokens.size() == 3); assert(tokens.size() == 3);
input.attrs.insert_or_assign("rev", Hash::parseAny(tokens[0], htSHA1).gitRev()); input.attrs.insert_or_assign("rev", Hash::parseAny(tokens[0], htSHA1).gitRev());
@ -263,7 +293,7 @@ struct MercurialInputScheme : InputScheme
Path tmpDir = createTempDir(); Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir, true); AutoDelete delTmpDir(tmpDir, true);
runProgram("hg", true, { "archive", "-R", cacheDir, "-r", input.getRev()->gitRev(), tmpDir }); runHg({ "archive", "-R", cacheDir, "-r", input.getRev()->gitRev(), tmpDir });
deletePath(tmpDir + "/.hg_archival.txt"); deletePath(tmpDir + "/.hg_archival.txt");

View file

@ -15,6 +15,9 @@ hg init $repo
echo '[ui]' >> $repo/.hg/hgrc echo '[ui]' >> $repo/.hg/hgrc
echo 'username = Foobar <foobar@example.org>' >> $repo/.hg/hgrc echo 'username = Foobar <foobar@example.org>' >> $repo/.hg/hgrc
# Set ui.tweakdefaults to ensure HGPLAIN is being set.
echo 'tweakdefaults = True' >> $repo/.hg/hgrc
echo utrecht > $repo/hello echo utrecht > $repo/hello
touch $repo/.hgignore touch $repo/.hgignore
hg add --cwd $repo hello .hgignore hg add --cwd $repo hello .hgignore