cmCTestHG.cxx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCTestHG.h"
  4. #include "cmCTest.h"
  5. #include "cmCTestVC.h"
  6. #include "cmProcessTools.h"
  7. #include "cmSystemTools.h"
  8. #include "cmXMLParser.h"
  9. #include "cmsys/RegularExpression.hxx"
  10. #include <ostream>
  11. #include <vector>
  12. cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log)
  13. : cmCTestGlobalVC(ct, log)
  14. {
  15. this->PriorRev = this->Unknown;
  16. }
  17. cmCTestHG::~cmCTestHG()
  18. {
  19. }
  20. class cmCTestHG::IdentifyParser : public cmCTestVC::LineParser
  21. {
  22. public:
  23. IdentifyParser(cmCTestHG* hg, const char* prefix, std::string& rev)
  24. : Rev(rev)
  25. {
  26. this->SetLog(&hg->Log, prefix);
  27. this->RegexIdentify.compile("^([0-9a-f]+)");
  28. }
  29. private:
  30. std::string& Rev;
  31. cmsys::RegularExpression RegexIdentify;
  32. bool ProcessLine() override
  33. {
  34. if (this->RegexIdentify.find(this->Line)) {
  35. this->Rev = this->RegexIdentify.match(1);
  36. return false;
  37. }
  38. return true;
  39. }
  40. };
  41. class cmCTestHG::StatusParser : public cmCTestVC::LineParser
  42. {
  43. public:
  44. StatusParser(cmCTestHG* hg, const char* prefix)
  45. : HG(hg)
  46. {
  47. this->SetLog(&hg->Log, prefix);
  48. this->RegexStatus.compile("([MARC!?I]) (.*)");
  49. }
  50. private:
  51. cmCTestHG* HG;
  52. cmsys::RegularExpression RegexStatus;
  53. bool ProcessLine() override
  54. {
  55. if (this->RegexStatus.find(this->Line)) {
  56. this->DoPath(this->RegexStatus.match(1)[0], this->RegexStatus.match(2));
  57. }
  58. return true;
  59. }
  60. void DoPath(char status, std::string const& path)
  61. {
  62. if (path.empty()) {
  63. return;
  64. }
  65. // See "hg help status". Note that there is no 'conflict' status.
  66. switch (status) {
  67. case 'M':
  68. case 'A':
  69. case '!':
  70. case 'R':
  71. this->HG->DoModification(PathModified, path);
  72. break;
  73. case 'I':
  74. case '?':
  75. case 'C':
  76. case ' ':
  77. default:
  78. break;
  79. }
  80. }
  81. };
  82. std::string cmCTestHG::GetWorkingRevision()
  83. {
  84. // Run plumbing "hg identify" to get work tree revision.
  85. const char* hg = this->CommandLineTool.c_str();
  86. const char* hg_identify[] = { hg, "identify", "-i", nullptr };
  87. std::string rev;
  88. IdentifyParser out(this, "rev-out> ", rev);
  89. OutputLogger err(this->Log, "rev-err> ");
  90. this->RunChild(hg_identify, &out, &err);
  91. return rev;
  92. }
  93. bool cmCTestHG::NoteOldRevision()
  94. {
  95. this->OldRevision = this->GetWorkingRevision();
  96. cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
  97. << this->OldRevision << "\n");
  98. this->PriorRev.Rev = this->OldRevision;
  99. return true;
  100. }
  101. bool cmCTestHG::NoteNewRevision()
  102. {
  103. this->NewRevision = this->GetWorkingRevision();
  104. cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
  105. << this->NewRevision << "\n");
  106. return true;
  107. }
  108. bool cmCTestHG::UpdateImpl()
  109. {
  110. // Use "hg pull" followed by "hg update" to update the working tree.
  111. {
  112. const char* hg = this->CommandLineTool.c_str();
  113. const char* hg_pull[] = { hg, "pull", "-v", nullptr };
  114. OutputLogger out(this->Log, "pull-out> ");
  115. OutputLogger err(this->Log, "pull-err> ");
  116. this->RunChild(&hg_pull[0], &out, &err);
  117. }
  118. // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
  119. std::vector<char const*> hg_update;
  120. hg_update.push_back(this->CommandLineTool.c_str());
  121. hg_update.push_back("update");
  122. hg_update.push_back("-v");
  123. // Add user-specified update options.
  124. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  125. if (opts.empty()) {
  126. opts = this->CTest->GetCTestConfiguration("HGUpdateOptions");
  127. }
  128. std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str());
  129. for (std::string const& arg : args) {
  130. hg_update.push_back(arg.c_str());
  131. }
  132. // Sentinel argument.
  133. hg_update.push_back(nullptr);
  134. OutputLogger out(this->Log, "update-out> ");
  135. OutputLogger err(this->Log, "update-err> ");
  136. return this->RunUpdateCommand(&hg_update[0], &out, &err);
  137. }
  138. class cmCTestHG::LogParser : public cmCTestVC::OutputLogger,
  139. private cmXMLParser
  140. {
  141. public:
  142. LogParser(cmCTestHG* hg, const char* prefix)
  143. : OutputLogger(hg->Log, prefix)
  144. , HG(hg)
  145. {
  146. this->InitializeParser();
  147. }
  148. ~LogParser() override { this->CleanupParser(); }
  149. private:
  150. cmCTestHG* HG;
  151. typedef cmCTestHG::Revision Revision;
  152. typedef cmCTestHG::Change Change;
  153. Revision Rev;
  154. std::vector<Change> Changes;
  155. Change CurChange;
  156. std::vector<char> CData;
  157. bool ProcessChunk(const char* data, int length) override
  158. {
  159. this->OutputLogger::ProcessChunk(data, length);
  160. this->ParseChunk(data, length);
  161. return true;
  162. }
  163. void StartElement(const std::string& name, const char** atts) override
  164. {
  165. this->CData.clear();
  166. if (name == "logentry") {
  167. this->Rev = Revision();
  168. if (const char* rev = this->FindAttribute(atts, "revision")) {
  169. this->Rev.Rev = rev;
  170. }
  171. this->Changes.clear();
  172. }
  173. }
  174. void CharacterDataHandler(const char* data, int length) override
  175. {
  176. this->CData.insert(this->CData.end(), data, data + length);
  177. }
  178. void EndElement(const std::string& name) override
  179. {
  180. if (name == "logentry") {
  181. this->HG->DoRevision(this->Rev, this->Changes);
  182. } else if (!this->CData.empty() && name == "author") {
  183. this->Rev.Author.assign(&this->CData[0], this->CData.size());
  184. } else if (!this->CData.empty() && name == "email") {
  185. this->Rev.EMail.assign(&this->CData[0], this->CData.size());
  186. } else if (!this->CData.empty() && name == "date") {
  187. this->Rev.Date.assign(&this->CData[0], this->CData.size());
  188. } else if (!this->CData.empty() && name == "msg") {
  189. this->Rev.Log.assign(&this->CData[0], this->CData.size());
  190. } else if (!this->CData.empty() && name == "files") {
  191. std::vector<std::string> paths = this->SplitCData();
  192. for (std::string const& path : paths) {
  193. // Updated by default, will be modified using file_adds and
  194. // file_dels.
  195. this->CurChange = Change('U');
  196. this->CurChange.Path = path;
  197. this->Changes.push_back(this->CurChange);
  198. }
  199. } else if (!this->CData.empty() && name == "file_adds") {
  200. std::string added_paths(this->CData.begin(), this->CData.end());
  201. for (Change& change : this->Changes) {
  202. if (added_paths.find(change.Path) != std::string::npos) {
  203. change.Action = 'A';
  204. }
  205. }
  206. } else if (!this->CData.empty() && name == "file_dels") {
  207. std::string added_paths(this->CData.begin(), this->CData.end());
  208. for (Change& change : this->Changes) {
  209. if (added_paths.find(change.Path) != std::string::npos) {
  210. change.Action = 'D';
  211. }
  212. }
  213. }
  214. this->CData.clear();
  215. }
  216. std::vector<std::string> SplitCData()
  217. {
  218. std::vector<std::string> output;
  219. std::string currPath;
  220. for (char i : this->CData) {
  221. if (i != ' ') {
  222. currPath += i;
  223. } else {
  224. output.push_back(currPath);
  225. currPath.clear();
  226. }
  227. }
  228. output.push_back(currPath);
  229. return output;
  230. }
  231. void ReportError(int /*line*/, int /*column*/, const char* msg) override
  232. {
  233. this->HG->Log << "Error parsing hg log xml: " << msg << "\n";
  234. }
  235. };
  236. bool cmCTestHG::LoadRevisions()
  237. {
  238. // Use 'hg log' to get revisions in a xml format.
  239. //
  240. // TODO: This should use plumbing or python code to be more precise.
  241. // The "list of strings" templates like {files} will not work when
  242. // the project has spaces in the path. Also, they may not have
  243. // proper XML escapes.
  244. std::string range = this->OldRevision + ":" + this->NewRevision;
  245. const char* hg = this->CommandLineTool.c_str();
  246. const char* hgXMLTemplate = "<logentry\n"
  247. " revision=\"{node|short}\">\n"
  248. " <author>{author|person}</author>\n"
  249. " <email>{author|email}</email>\n"
  250. " <date>{date|isodate}</date>\n"
  251. " <msg>{desc}</msg>\n"
  252. " <files>{files}</files>\n"
  253. " <file_adds>{file_adds}</file_adds>\n"
  254. " <file_dels>{file_dels}</file_dels>\n"
  255. "</logentry>\n";
  256. const char* hg_log[] = {
  257. hg, "log", "--removed", "-r", range.c_str(),
  258. "--template", hgXMLTemplate, nullptr
  259. };
  260. LogParser out(this, "log-out> ");
  261. out.Process("<?xml version=\"1.0\"?>\n"
  262. "<log>\n");
  263. OutputLogger err(this->Log, "log-err> ");
  264. this->RunChild(hg_log, &out, &err);
  265. out.Process("</log>\n");
  266. return true;
  267. }
  268. bool cmCTestHG::LoadModifications()
  269. {
  270. // Use 'hg status' to get modified files.
  271. const char* hg = this->CommandLineTool.c_str();
  272. const char* hg_status[] = { hg, "status", nullptr };
  273. StatusParser out(this, "status-out> ");
  274. OutputLogger err(this->Log, "status-err> ");
  275. this->RunChild(hg_status, &out, &err);
  276. return true;
  277. }