1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
/* Copyright 2017-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
#include "Mercurial.h"
#include "ChildProcess.h"
#include "Logging.h"
#include "watchman.h"
// Capability indicating support for the mercurial SCM
W_CAP_REG("scm-hg")
namespace watchman {
using Options = ChildProcess::Options;
Mercurial::infoCache::infoCache(std::string path) : dirStatePath(path) {
memset(&dirstate, 0, sizeof(dirstate));
}
w_string Mercurial::infoCache::lookupMergeBase(const std::string& commitId) {
if (dotChanged()) {
watchman::log(
watchman::DBG, "Blowing mergeBases cache because dirstate changed\n");
mergeBases.clear();
return nullptr;
}
auto it = mergeBases.find(commitId);
if (it != mergeBases.end()) {
watchman::log(watchman::DBG, "mergeBases cache hit for ", commitId, "\n");
return it->second;
}
watchman::log(watchman::DBG, "mergeBases cache miss for ", commitId, "\n");
return nullptr;
}
bool Mercurial::infoCache::dotChanged() {
bool result;
try {
auto info = getFileInformation(dirStatePath.c_str(),
CaseSensitivity::CaseSensitive);
if (info.size != dirstate.size ||
info.mtime.tv_sec != dirstate.mtime.tv_sec ||
info.mtime.tv_nsec != dirstate.mtime.tv_nsec) {
log(DBG, "mergeBases stat(", dirStatePath, ") info differs\n");
result = true;
} else {
result = false;
log(DBG, "mergeBases stat(", dirStatePath, ") info same\n");
}
dirstate = info;
} catch (const std::system_error &exc) {
// Failed to stat, so assume that it changed
log(DBG, "mergeBases stat(", dirStatePath, ") failed: ", exc.what(), "\n");
result = true;
}
return result;
}
Mercurial::Mercurial(w_string_piece rootPath, w_string_piece scmRoot)
: SCM(rootPath, scmRoot),
cache_(infoCache(to<std::string>(getSCMRoot(), "/.hg/dirstate"))) {}
w_string Mercurial::mergeBaseWith(w_string_piece commitId) const {
std::string idString(commitId.data(), commitId.size());
{
auto cache = cache_.wlock();
auto result = cache->lookupMergeBase(idString);
if (result) {
watchman::log(
watchman::DBG,
"Using cached mergeBase value of ",
result,
" for commitId ",
commitId,
" because dirstate file is unchanged\n");
return result;
}
}
Options opt;
// Ensure that the hgrc doesn't mess with the behavior
// of the commands that we're runing.
opt.environment().set("HGPLAIN", w_string("1"));
opt.nullStdin();
opt.pipeStdout();
opt.pipeStderr();
opt.chdir(getRootPath());
auto revset = to<std::string>("ancestor(.,", commitId, ")");
ChildProcess proc(
{"hg", "log", "-T", "{node}", "-r", revset}, std::move(opt));
auto outputs = proc.communicate();
auto status = proc.wait();
if (status) {
throw std::runtime_error(to<std::string>(
"failed query for the merge base; command returned with status ",
status,
", output=",
outputs.first,
" error=",
outputs.second));
}
if (outputs.first.size() != 40) {
throw std::runtime_error(to<std::string>(
"expected merge base to be a 40 character string, got ",
outputs.first));
}
{
auto cache = cache_.wlock();
cache->mergeBases[idString] = outputs.first;
}
return outputs.first;
}
std::vector<w_string> Mercurial::getFilesChangedSinceMergeBaseWith(
w_string_piece commitId) const {
Options opt;
// Ensure that the hgrc doesn't mess with the behavior
// of the commands that we're runing.
opt.environment().set("HGPLAIN", w_string("1"));
opt.nullStdin();
opt.pipeStdout();
opt.pipeStderr();
opt.chdir(getRootPath());
// The "" argument at the end causes paths to be printed out relative to the
// cwd (set to root path above).
ChildProcess proc(
{"hg", "status", "-n", "--rev", commitId, ""}, std::move(opt));
auto outputs = proc.communicate();
std::vector<w_string> lines;
w_string_piece(outputs.first).split(lines, '\n');
auto status = proc.wait();
if (status) {
throw std::runtime_error(to<std::string>(
"failed query for the hg status; command returned with status ",
status,
" out=",
outputs.first,
" err=",
outputs.second));
}
return lines;
}
}
|