From 226116f48272dbe25bb9d3b8fa138cf6b10ec8ef Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Mon, 23 Nov 2020 16:12:33 +0000 Subject: [PATCH] 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. --- src/libfetchers/mercurial.cc | 54 ++++++++++++++++++++++++++++-------- tests/fetchMercurial.sh | 3 ++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 41f60c45c..07a51059d 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -11,6 +11,36 @@ using namespace std::string_literals; 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 & 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 { std::optional inputFromURL(const ParsedURL & url) override @@ -100,11 +130,11 @@ struct MercurialInputScheme : InputScheme assert(sourcePath); // FIXME: shut up if file is already tracked. - runProgram("hg", true, + runHg( { "add", *sourcePath + "/" + std::string(file) }); if (commitMsg) - runProgram("hg", true, + runHg( { "commit", *sourcePath + "/" + std::string(file), "-m", *commitMsg }); } @@ -130,7 +160,7 @@ struct MercurialInputScheme : InputScheme 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) { @@ -143,10 +173,10 @@ struct MercurialInputScheme : InputScheme if (settings.warnDirty) 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>( - 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 { assert(hasPrefix(p, actualUrl)); @@ -224,33 +254,33 @@ struct MercurialInputScheme : InputScheme if (!(input.getRev() && pathExists(cacheDir) && 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")) { Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", actualUrl)); if (pathExists(cacheDir)) { try { - runProgram("hg", true, { "pull", "-R", cacheDir, "--", actualUrl }); + runHg({ "pull", "-R", cacheDir, "--", actualUrl }); } 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, "--", actualUrl }); + runHg({ "recover", "-R", cacheDir }); + runHg({ "pull", "-R", cacheDir, "--", actualUrl }); } else { throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status))); } } } else { createDirs(dirOf(cacheDir)); - runProgram("hg", true, { "clone", "--noupdate", "--", actualUrl, cacheDir }); + runHg({ "clone", "--noupdate", "--", actualUrl, cacheDir }); } } auto tokens = tokenizeString>( - 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); input.attrs.insert_or_assign("rev", Hash::parseAny(tokens[0], htSHA1).gitRev()); @@ -263,7 +293,7 @@ struct MercurialInputScheme : InputScheme Path tmpDir = createTempDir(); 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"); diff --git a/tests/fetchMercurial.sh b/tests/fetchMercurial.sh index af8ef8d5b..d8a4e09d2 100644 --- a/tests/fetchMercurial.sh +++ b/tests/fetchMercurial.sh @@ -15,6 +15,9 @@ hg init $repo echo '[ui]' >> $repo/.hg/hgrc echo 'username = Foobar ' >> $repo/.hg/hgrc +# Set ui.tweakdefaults to ensure HGPLAIN is being set. +echo 'tweakdefaults = True' >> $repo/.hg/hgrc + echo utrecht > $repo/hello touch $repo/.hgignore hg add --cwd $repo hello .hgignore