cmCTestHG.cxx 9.9 KB

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