cmCTestSVN.cxx 14 KB

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