cmCTestHG.cxx 9.4 KB

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