cmCTestHG.cxx 10 KB

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