cmCTestSVN.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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 "cmCTestSVN.h"
  14. #include "cmCTest.h"
  15. #include "cmSystemTools.h"
  16. #include "cmXMLParser.h"
  17. #include "cmXMLSafe.h"
  18. #include <cmsys/RegularExpression.hxx>
  19. //----------------------------------------------------------------------------
  20. cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log):
  21. cmCTestGlobalVC(ct, log)
  22. {
  23. this->PriorRev = this->Unknown;
  24. }
  25. //----------------------------------------------------------------------------
  26. cmCTestSVN::~cmCTestSVN()
  27. {
  28. }
  29. //----------------------------------------------------------------------------
  30. void cmCTestSVN::CleanupImpl()
  31. {
  32. const char* svn = this->CommandLineTool.c_str();
  33. const char* svn_cleanup[] = {svn, "cleanup", 0};
  34. OutputLogger out(this->Log, "cleanup-out> ");
  35. OutputLogger err(this->Log, "cleanup-err> ");
  36. this->RunChild(svn_cleanup, &out, &err);
  37. }
  38. //----------------------------------------------------------------------------
  39. class cmCTestSVN::InfoParser: public cmCTestVC::LineParser
  40. {
  41. public:
  42. InfoParser(cmCTestSVN* svn, const char* prefix, std::string& rev):
  43. SVN(svn), Rev(rev)
  44. {
  45. this->SetLog(&svn->Log, prefix);
  46. this->RegexRev.compile("^Revision: ([0-9]+)");
  47. this->RegexURL.compile("^URL: +([^ ]+) *$");
  48. this->RegexRoot.compile("^Repository Root: +([^ ]+) *$");
  49. }
  50. private:
  51. cmCTestSVN* SVN;
  52. std::string& Rev;
  53. cmsys::RegularExpression RegexRev;
  54. cmsys::RegularExpression RegexURL;
  55. cmsys::RegularExpression RegexRoot;
  56. virtual bool ProcessLine()
  57. {
  58. if(this->RegexRev.find(this->Line))
  59. {
  60. this->Rev = this->RegexRev.match(1);
  61. }
  62. else if(this->RegexURL.find(this->Line))
  63. {
  64. this->SVN->URL = this->RegexURL.match(1);
  65. }
  66. else if(this->RegexRoot.find(this->Line))
  67. {
  68. this->SVN->Root = this->RegexRoot.match(1);
  69. }
  70. return true;
  71. }
  72. };
  73. //----------------------------------------------------------------------------
  74. static bool cmCTestSVNPathStarts(std::string const& p1, std::string const& p2)
  75. {
  76. // Does path p1 start with path p2?
  77. if(p1.size() == p2.size())
  78. {
  79. return p1 == p2;
  80. }
  81. else if(p1.size() > p2.size() && p1[p2.size()] == '/')
  82. {
  83. return strncmp(p1.c_str(), p2.c_str(), p2.size()) == 0;
  84. }
  85. else
  86. {
  87. return false;
  88. }
  89. }
  90. //----------------------------------------------------------------------------
  91. std::string cmCTestSVN::LoadInfo()
  92. {
  93. // Run "svn info" to get the repository info from the work tree.
  94. const char* svn = this->CommandLineTool.c_str();
  95. const char* svn_info[] = {svn, "info", 0};
  96. std::string rev;
  97. InfoParser out(this, "info-out> ", rev);
  98. OutputLogger err(this->Log, "info-err> ");
  99. this->RunChild(svn_info, &out, &err);
  100. return rev;
  101. }
  102. //----------------------------------------------------------------------------
  103. void cmCTestSVN::NoteOldRevision()
  104. {
  105. this->OldRevision = this->LoadInfo();
  106. this->Log << "Revision before update: " << this->OldRevision << "\n";
  107. cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
  108. << this->OldRevision << "\n");
  109. this->PriorRev.Rev = this->OldRevision;
  110. }
  111. //----------------------------------------------------------------------------
  112. void cmCTestSVN::NoteNewRevision()
  113. {
  114. this->NewRevision = this->LoadInfo();
  115. this->Log << "Revision after update: " << this->NewRevision << "\n";
  116. cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
  117. << this->NewRevision << "\n");
  118. // this->Root = ""; // uncomment to test GuessBase
  119. this->Log << "URL = " << this->URL << "\n";
  120. this->Log << "Root = " << this->Root << "\n";
  121. // Compute the base path the working tree has checked out under
  122. // the repository root.
  123. if(!this->Root.empty() && cmCTestSVNPathStarts(this->URL, this->Root))
  124. {
  125. this->Base = cmCTest::DecodeURL(this->URL.substr(this->Root.size()));
  126. this->Base += "/";
  127. }
  128. this->Log << "Base = " << this->Base << "\n";
  129. }
  130. //----------------------------------------------------------------------------
  131. void cmCTestSVN::GuessBase(std::vector<Change> const& changes)
  132. {
  133. // Subversion did not give us a good repository root so we need to
  134. // guess the base path from the URL and the paths in a revision with
  135. // changes under it.
  136. // Consider each possible URL suffix from longest to shortest.
  137. for(std::string::size_type slash = this->URL.find('/');
  138. this->Base.empty() && slash != std::string::npos;
  139. slash = this->URL.find('/', slash+1))
  140. {
  141. // If the URL suffix is a prefix of at least one path then it is the base.
  142. std::string base = cmCTest::DecodeURL(this->URL.substr(slash));
  143. for(std::vector<Change>::const_iterator ci = changes.begin();
  144. this->Base.empty() && ci != changes.end(); ++ci)
  145. {
  146. if(cmCTestSVNPathStarts(ci->Path, base))
  147. {
  148. this->Base = base;
  149. }
  150. }
  151. }
  152. // We always append a slash so that we know paths beginning in the
  153. // base lie under its path. If no base was found then the working
  154. // tree must be a checkout of the entire repo and this will match
  155. // the leading slash in all paths.
  156. this->Base += "/";
  157. this->Log << "Guessed Base = " << this->Base << "\n";
  158. }
  159. //----------------------------------------------------------------------------
  160. const char* cmCTestSVN::LocalPath(std::string const& path)
  161. {
  162. if(path.size() > this->Base.size() &&
  163. strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0)
  164. {
  165. // This path lies under the base, so return a relative path.
  166. return path.c_str() + this->Base.size();
  167. }
  168. else
  169. {
  170. // This path does not lie under the base, so ignore it.
  171. return 0;
  172. }
  173. }
  174. //----------------------------------------------------------------------------
  175. class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser
  176. {
  177. public:
  178. UpdateParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  179. {
  180. this->SetLog(&svn->Log, prefix);
  181. this->RegexUpdate.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$");
  182. }
  183. private:
  184. cmCTestSVN* SVN;
  185. cmsys::RegularExpression RegexUpdate;
  186. bool ProcessLine()
  187. {
  188. if(this->RegexUpdate.find(this->Line))
  189. {
  190. this->DoPath(this->RegexUpdate.match(1)[0],
  191. this->RegexUpdate.match(2)[0],
  192. this->RegexUpdate.match(3));
  193. }
  194. return true;
  195. }
  196. void DoPath(char path_status, char prop_status, std::string const& path)
  197. {
  198. char status = (path_status != ' ')? path_status : prop_status;
  199. std::string dir = cmSystemTools::GetFilenamePath(path);
  200. std::string name = cmSystemTools::GetFilenameName(path);
  201. // See "svn help update".
  202. switch(status)
  203. {
  204. case 'G':
  205. this->SVN->Dirs[dir][name].Status = PathModified;
  206. break;
  207. case 'C':
  208. this->SVN->Dirs[dir][name].Status = PathConflicting;
  209. break;
  210. case 'A': case 'D': case 'U':
  211. this->SVN->Dirs[dir][name].Status = PathUpdated;
  212. break;
  213. case 'E': // TODO?
  214. case '?': case ' ': default:
  215. break;
  216. }
  217. }
  218. };
  219. //----------------------------------------------------------------------------
  220. bool cmCTestSVN::UpdateImpl()
  221. {
  222. // Get user-specified update options.
  223. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  224. if(opts.empty())
  225. {
  226. opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions");
  227. }
  228. std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
  229. // Specify the start time for nightly testing.
  230. if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
  231. {
  232. args.push_back("-r{" + this->GetNightlyTime() + " +0000}");
  233. }
  234. std::vector<char const*> svn_update;
  235. svn_update.push_back(this->CommandLineTool.c_str());
  236. svn_update.push_back("update");
  237. svn_update.push_back("--non-interactive");
  238. for(std::vector<cmStdString>::const_iterator ai = args.begin();
  239. ai != args.end(); ++ai)
  240. {
  241. svn_update.push_back(ai->c_str());
  242. }
  243. svn_update.push_back(0);
  244. UpdateParser out(this, "up-out> ");
  245. OutputLogger err(this->Log, "up-err> ");
  246. return this->RunUpdateCommand(&svn_update[0], &out, &err);
  247. }
  248. //----------------------------------------------------------------------------
  249. class cmCTestSVN::LogParser: public cmCTestVC::OutputLogger,
  250. private cmXMLParser
  251. {
  252. public:
  253. LogParser(cmCTestSVN* svn, const char* prefix):
  254. OutputLogger(svn->Log, prefix), SVN(svn) { this->InitializeParser(); }
  255. ~LogParser() { this->CleanupParser(); }
  256. private:
  257. cmCTestSVN* SVN;
  258. typedef cmCTestSVN::Revision Revision;
  259. typedef cmCTestSVN::Change Change;
  260. Revision Rev;
  261. std::vector<Change> Changes;
  262. Change CurChange;
  263. std::vector<char> CData;
  264. virtual bool ProcessChunk(const char* data, int length)
  265. {
  266. this->OutputLogger::ProcessChunk(data, length);
  267. this->ParseChunk(data, length);
  268. return true;
  269. }
  270. virtual void StartElement(const char* name, const char** atts)
  271. {
  272. this->CData.clear();
  273. if(strcmp(name, "logentry") == 0)
  274. {
  275. this->Rev = Revision();
  276. if(const char* rev = this->FindAttribute(atts, "revision"))
  277. {
  278. this->Rev.Rev = rev;
  279. }
  280. this->Changes.clear();
  281. }
  282. else if(strcmp(name, "path") == 0)
  283. {
  284. this->CurChange = Change();
  285. if(const char* action = this->FindAttribute(atts, "action"))
  286. {
  287. this->CurChange.Action = action[0];
  288. }
  289. }
  290. }
  291. virtual void CharacterDataHandler(const char* data, int length)
  292. {
  293. this->CData.insert(this->CData.end(), data, data+length);
  294. }
  295. virtual void EndElement(const char* name)
  296. {
  297. if(strcmp(name, "logentry") == 0)
  298. {
  299. this->SVN->DoRevision(this->Rev, this->Changes);
  300. }
  301. else if(strcmp(name, "path") == 0 && !this->CData.empty())
  302. {
  303. this->CurChange.Path.assign(&this->CData[0], this->CData.size());
  304. this->Changes.push_back(this->CurChange);
  305. }
  306. else if(strcmp(name, "author") == 0 && !this->CData.empty())
  307. {
  308. this->Rev.Author.assign(&this->CData[0], this->CData.size());
  309. }
  310. else if(strcmp(name, "date") == 0 && !this->CData.empty())
  311. {
  312. this->Rev.Date.assign(&this->CData[0], this->CData.size());
  313. }
  314. else if(strcmp(name, "msg") == 0 && !this->CData.empty())
  315. {
  316. this->Rev.Log.assign(&this->CData[0], this->CData.size());
  317. }
  318. this->CData.clear();
  319. }
  320. virtual void ReportError(int, int, const char* msg)
  321. {
  322. this->SVN->Log << "Error parsing svn log xml: " << msg << "\n";
  323. }
  324. };
  325. //----------------------------------------------------------------------------
  326. void cmCTestSVN::LoadRevisions()
  327. {
  328. // We are interested in every revision included in the update.
  329. std::string revs;
  330. if(atoi(this->OldRevision.c_str()) < atoi(this->NewRevision.c_str()))
  331. {
  332. revs = "-r" + this->OldRevision + ":" + this->NewRevision;
  333. }
  334. else
  335. {
  336. revs = "-r" + this->NewRevision;
  337. }
  338. // Run "svn log" to get all global revisions of interest.
  339. const char* svn = this->CommandLineTool.c_str();
  340. const char* svn_log[] = {svn, "log", "--xml", "-v", revs.c_str(), 0};
  341. {
  342. LogParser out(this, "log-out> ");
  343. OutputLogger err(this->Log, "log-err> ");
  344. this->RunChild(svn_log, &out, &err);
  345. }
  346. }
  347. //----------------------------------------------------------------------------
  348. void cmCTestSVN::DoRevision(Revision const& revision,
  349. std::vector<Change> const& changes)
  350. {
  351. // Guess the base checkout path from the changes if necessary.
  352. if(this->Base.empty() && !changes.empty())
  353. {
  354. this->GuessBase(changes);
  355. }
  356. this->cmCTestGlobalVC::DoRevision(revision, changes);
  357. }
  358. //----------------------------------------------------------------------------
  359. class cmCTestSVN::StatusParser: public cmCTestVC::LineParser
  360. {
  361. public:
  362. StatusParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  363. {
  364. this->SetLog(&svn->Log, prefix);
  365. this->RegexStatus.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$");
  366. }
  367. private:
  368. cmCTestSVN* SVN;
  369. cmsys::RegularExpression RegexStatus;
  370. bool ProcessLine()
  371. {
  372. if(this->RegexStatus.find(this->Line))
  373. {
  374. this->DoPath(this->RegexStatus.match(1)[0],
  375. this->RegexStatus.match(2)[0],
  376. this->RegexStatus.match(3));
  377. }
  378. return true;
  379. }
  380. void DoPath(char path_status, char prop_status, std::string const& path)
  381. {
  382. char status = (path_status != ' ')? path_status : prop_status;
  383. // See "svn help status".
  384. switch(status)
  385. {
  386. case 'M': case '!': case 'A': case 'D': case 'R': case 'X':
  387. this->SVN->DoModification(PathModified, path);
  388. break;
  389. case 'C': case '~':
  390. this->SVN->DoModification(PathConflicting, path);
  391. break;
  392. case 'I': case '?': case ' ': default:
  393. break;
  394. }
  395. }
  396. };
  397. //----------------------------------------------------------------------------
  398. void cmCTestSVN::LoadModifications()
  399. {
  400. // Run "svn status" which reports local modifications.
  401. const char* svn = this->CommandLineTool.c_str();
  402. const char* svn_status[] = {svn, "status", "--non-interactive", 0};
  403. StatusParser out(this, "status-out> ");
  404. OutputLogger err(this->Log, "status-err> ");
  405. this->RunChild(svn_status, &out, &err);
  406. }