cmCTestSVN.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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. struct cmCTestSVN::Revision: public cmCTestVC::Revision
  17. {
  18. cmCTestSVN::SVNInfo* SVNInfo;
  19. };
  20. //----------------------------------------------------------------------------
  21. cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log):
  22. cmCTestGlobalVC(ct, log)
  23. {
  24. this->PriorRev = this->Unknown;
  25. }
  26. //----------------------------------------------------------------------------
  27. cmCTestSVN::~cmCTestSVN()
  28. {
  29. }
  30. //----------------------------------------------------------------------------
  31. void cmCTestSVN::CleanupImpl()
  32. {
  33. std::vector<const char*> svn_cleanup;
  34. svn_cleanup.push_back("cleanup");
  35. OutputLogger out(this->Log, "cleanup-out> ");
  36. OutputLogger err(this->Log, "cleanup-err> ");
  37. this->RunSVNCommand(svn_cleanup, &out, &err);
  38. }
  39. //----------------------------------------------------------------------------
  40. class cmCTestSVN::InfoParser: public cmCTestVC::LineParser
  41. {
  42. public:
  43. InfoParser(cmCTestSVN* svn,
  44. const char* prefix,
  45. std::string& rev,
  46. SVNInfo& svninfo):
  47. Rev(rev), SVNRepo(svninfo)
  48. {
  49. this->SetLog(&svn->Log, prefix);
  50. this->RegexRev.compile("^Revision: ([0-9]+)");
  51. this->RegexURL.compile("^URL: +([^ ]+) *$");
  52. this->RegexRoot.compile("^Repository Root: +([^ ]+) *$");
  53. }
  54. private:
  55. std::string& Rev;
  56. cmCTestSVN::SVNInfo& SVNRepo;
  57. cmsys::RegularExpression RegexRev;
  58. cmsys::RegularExpression RegexURL;
  59. cmsys::RegularExpression RegexRoot;
  60. virtual bool ProcessLine()
  61. {
  62. if(this->RegexRev.find(this->Line))
  63. {
  64. this->Rev = this->RegexRev.match(1);
  65. }
  66. else if(this->RegexURL.find(this->Line))
  67. {
  68. this->SVNRepo.URL = this->RegexURL.match(1);
  69. }
  70. else if(this->RegexRoot.find(this->Line))
  71. {
  72. this->SVNRepo.Root = this->RegexRoot.match(1);
  73. }
  74. return true;
  75. }
  76. };
  77. //----------------------------------------------------------------------------
  78. static bool cmCTestSVNPathStarts(std::string const& p1, std::string const& p2)
  79. {
  80. // Does path p1 start with path p2?
  81. if(p1.size() == p2.size())
  82. {
  83. return p1 == p2;
  84. }
  85. else if(p1.size() > p2.size() && p1[p2.size()] == '/')
  86. {
  87. return strncmp(p1.c_str(), p2.c_str(), p2.size()) == 0;
  88. }
  89. else
  90. {
  91. return false;
  92. }
  93. }
  94. //----------------------------------------------------------------------------
  95. std::string cmCTestSVN::LoadInfo(SVNInfo& svninfo)
  96. {
  97. // Run "svn info" to get the repository info from the work tree.
  98. std::vector<const char*> svn_info;
  99. svn_info.push_back("info");
  100. svn_info.push_back(svninfo.LocalPath.c_str());
  101. std::string rev;
  102. InfoParser out(this, "info-out> ", rev, svninfo);
  103. OutputLogger err(this->Log, "info-err> ");
  104. this->RunSVNCommand(svn_info, &out, &err);
  105. return rev;
  106. }
  107. //----------------------------------------------------------------------------
  108. void cmCTestSVN::NoteOldRevision()
  109. {
  110. // Info for root repository
  111. this->Repositories.push_back( SVNInfo("") );
  112. this->RootInfo = &(this->Repositories.back());
  113. // Info for the external repositories
  114. this->LoadExternals();
  115. // Get info for all the repositories
  116. std::list<SVNInfo>::iterator itbeg = this->Repositories.begin();
  117. std::list<SVNInfo>::iterator itend = this->Repositories.end();
  118. for( ; itbeg != itend ; itbeg++)
  119. {
  120. SVNInfo& svninfo = *itbeg;
  121. svninfo.OldRevision = this->LoadInfo(svninfo);
  122. this->Log << "Revision for repository '" << svninfo.LocalPath
  123. << "' before update: " << svninfo.OldRevision << "\n";
  124. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  125. " Old revision of external repository '"
  126. << svninfo.LocalPath << "' is: "
  127. << svninfo.OldRevision << "\n");
  128. }
  129. // Set the global old revision to the one of the root
  130. this->OldRevision = this->RootInfo->OldRevision;
  131. this->PriorRev.Rev = this->OldRevision;
  132. }
  133. //----------------------------------------------------------------------------
  134. void cmCTestSVN::NoteNewRevision()
  135. {
  136. // Get info for the external repositories
  137. std::list<SVNInfo>::iterator itbeg = this->Repositories.begin();
  138. std::list<SVNInfo>::iterator itend = this->Repositories.end();
  139. for( ; itbeg != itend ; itbeg++)
  140. {
  141. SVNInfo& svninfo = *itbeg;
  142. svninfo.NewRevision = this->LoadInfo(svninfo);
  143. this->Log << "Revision for repository '" << svninfo.LocalPath
  144. << "' after update: " << svninfo.NewRevision << "\n";
  145. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  146. " New revision of external repository '"
  147. << svninfo.LocalPath << "' is: "
  148. << svninfo.NewRevision << "\n");
  149. // svninfo.Root = ""; // uncomment to test GuessBase
  150. this->Log << "Repository '" << svninfo.LocalPath
  151. << "' URL = " << svninfo.URL << "\n";
  152. this->Log << "Repository '" << svninfo.LocalPath
  153. << "' Root = " << svninfo.Root << "\n";
  154. // Compute the base path the working tree has checked out under
  155. // the repository root.
  156. if(!svninfo.Root.empty()
  157. && cmCTestSVNPathStarts(svninfo.URL, svninfo.Root))
  158. {
  159. svninfo.Base = cmCTest::DecodeURL(
  160. svninfo.URL.substr(svninfo.Root.size()));
  161. svninfo.Base += "/";
  162. }
  163. this->Log << "Repository '" << svninfo.LocalPath
  164. << "' Base = " << svninfo.Base << "\n";
  165. }
  166. // Set the global new revision to the one of the root
  167. this->NewRevision = this->RootInfo->NewRevision;
  168. }
  169. //----------------------------------------------------------------------------
  170. void cmCTestSVN::GuessBase(SVNInfo& svninfo,
  171. std::vector<Change> const& changes)
  172. {
  173. // Subversion did not give us a good repository root so we need to
  174. // guess the base path from the URL and the paths in a revision with
  175. // changes under it.
  176. // Consider each possible URL suffix from longest to shortest.
  177. for(std::string::size_type slash = svninfo.URL.find('/');
  178. svninfo.Base.empty() && slash != std::string::npos;
  179. slash = svninfo.URL.find('/', slash+1))
  180. {
  181. // If the URL suffix is a prefix of at least one path then it is the base.
  182. std::string base = cmCTest::DecodeURL(svninfo.URL.substr(slash));
  183. for(std::vector<Change>::const_iterator ci = changes.begin();
  184. svninfo.Base.empty() && ci != changes.end(); ++ci)
  185. {
  186. if(cmCTestSVNPathStarts(ci->Path, base))
  187. {
  188. svninfo.Base = base;
  189. }
  190. }
  191. }
  192. // We always append a slash so that we know paths beginning in the
  193. // base lie under its path. If no base was found then the working
  194. // tree must be a checkout of the entire repo and this will match
  195. // the leading slash in all paths.
  196. svninfo.Base += "/";
  197. this->Log << "Guessed Base = " << svninfo.Base << "\n";
  198. }
  199. //----------------------------------------------------------------------------
  200. class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser
  201. {
  202. public:
  203. UpdateParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  204. {
  205. this->SetLog(&svn->Log, prefix);
  206. this->RegexUpdate.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$");
  207. }
  208. private:
  209. cmCTestSVN* SVN;
  210. cmsys::RegularExpression RegexUpdate;
  211. bool ProcessLine()
  212. {
  213. if(this->RegexUpdate.find(this->Line))
  214. {
  215. this->DoPath(this->RegexUpdate.match(1)[0],
  216. this->RegexUpdate.match(2)[0],
  217. this->RegexUpdate.match(3));
  218. }
  219. return true;
  220. }
  221. void DoPath(char path_status, char prop_status, std::string const& path)
  222. {
  223. char status = (path_status != ' ')? path_status : prop_status;
  224. std::string dir = cmSystemTools::GetFilenamePath(path);
  225. std::string name = cmSystemTools::GetFilenameName(path);
  226. // See "svn help update".
  227. switch(status)
  228. {
  229. case 'G':
  230. this->SVN->Dirs[dir][name].Status = PathModified;
  231. break;
  232. case 'C':
  233. this->SVN->Dirs[dir][name].Status = PathConflicting;
  234. break;
  235. case 'A': case 'D': case 'U':
  236. this->SVN->Dirs[dir][name].Status = PathUpdated;
  237. break;
  238. case 'E': // TODO?
  239. case '?': case ' ': default:
  240. break;
  241. }
  242. }
  243. };
  244. //----------------------------------------------------------------------------
  245. bool cmCTestSVN::UpdateImpl()
  246. {
  247. // Get user-specified update options.
  248. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  249. if(opts.empty())
  250. {
  251. opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions");
  252. }
  253. std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str());
  254. // Specify the start time for nightly testing.
  255. if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
  256. {
  257. args.push_back("-r{" + this->GetNightlyTime() + " +0000}");
  258. }
  259. std::vector<char const*> svn_update;
  260. svn_update.push_back("update");
  261. for(std::vector<std::string>::const_iterator ai = args.begin();
  262. ai != args.end(); ++ai)
  263. {
  264. svn_update.push_back(ai->c_str());
  265. }
  266. UpdateParser out(this, "up-out> ");
  267. OutputLogger err(this->Log, "up-err> ");
  268. return this->RunSVNCommand(svn_update, &out, &err);
  269. }
  270. //----------------------------------------------------------------------------
  271. bool cmCTestSVN::RunSVNCommand(std::vector<char const*> const& parameters,
  272. OutputParser* out, OutputParser* err)
  273. {
  274. if(parameters.empty()) return false;
  275. std::vector<char const*> args;
  276. args.push_back(this->CommandLineTool.c_str());
  277. args.insert(args.end(), parameters.begin(), parameters.end());
  278. args.push_back("--non-interactive");
  279. std::string userOptions =
  280. this->CTest->GetCTestConfiguration("SVNOptions");
  281. std::vector<std::string> parsedUserOptions =
  282. cmSystemTools::ParseArguments(userOptions.c_str());
  283. for(std::vector<std::string>::iterator i = parsedUserOptions.begin();
  284. i != parsedUserOptions.end(); ++i)
  285. {
  286. args.push_back(i->c_str());
  287. }
  288. args.push_back(0);
  289. if(strcmp(parameters[0], "update") == 0)
  290. {
  291. return RunUpdateCommand(&args[0], out, err);
  292. }
  293. else
  294. {
  295. return RunChild(&args[0], out, err);
  296. }
  297. }
  298. //----------------------------------------------------------------------------
  299. class cmCTestSVN::LogParser: public cmCTestVC::OutputLogger,
  300. private cmXMLParser
  301. {
  302. public:
  303. LogParser(cmCTestSVN* svn, const char* prefix, SVNInfo& svninfo):
  304. OutputLogger(svn->Log, prefix), SVN(svn), SVNRepo(svninfo)
  305. { this->InitializeParser(); }
  306. ~LogParser() { this->CleanupParser(); }
  307. private:
  308. cmCTestSVN* SVN;
  309. cmCTestSVN::SVNInfo& SVNRepo;
  310. typedef cmCTestSVN::Revision Revision;
  311. typedef cmCTestSVN::Change Change;
  312. Revision Rev;
  313. std::vector<Change> Changes;
  314. Change CurChange;
  315. std::vector<char> CData;
  316. virtual bool ProcessChunk(const char* data, int length)
  317. {
  318. this->OutputLogger::ProcessChunk(data, length);
  319. this->ParseChunk(data, length);
  320. return true;
  321. }
  322. virtual void StartElement(const char* name, const char** atts)
  323. {
  324. this->CData.clear();
  325. if(strcmp(name, "logentry") == 0)
  326. {
  327. this->Rev = Revision();
  328. this->Rev.SVNInfo = &SVNRepo;
  329. if(const char* rev = this->FindAttribute(atts, "revision"))
  330. {
  331. this->Rev.Rev = rev;
  332. }
  333. this->Changes.clear();
  334. }
  335. else if(strcmp(name, "path") == 0)
  336. {
  337. this->CurChange = Change();
  338. if(const char* action = this->FindAttribute(atts, "action"))
  339. {
  340. this->CurChange.Action = action[0];
  341. }
  342. }
  343. }
  344. virtual void CharacterDataHandler(const char* data, int length)
  345. {
  346. this->CData.insert(this->CData.end(), data, data+length);
  347. }
  348. virtual void EndElement(const char* name)
  349. {
  350. if(strcmp(name, "logentry") == 0)
  351. {
  352. this->SVN->DoRevisionSVN(this->Rev, this->Changes);
  353. }
  354. else if(strcmp(name, "path") == 0 && !this->CData.empty())
  355. {
  356. std::string orig_path(&this->CData[0], this->CData.size());
  357. std::string new_path = SVNRepo.BuildLocalPath( orig_path );
  358. this->CurChange.Path.assign(new_path);
  359. this->Changes.push_back(this->CurChange);
  360. }
  361. else if(strcmp(name, "author") == 0 && !this->CData.empty())
  362. {
  363. this->Rev.Author.assign(&this->CData[0], this->CData.size());
  364. }
  365. else if(strcmp(name, "date") == 0 && !this->CData.empty())
  366. {
  367. this->Rev.Date.assign(&this->CData[0], this->CData.size());
  368. }
  369. else if(strcmp(name, "msg") == 0 && !this->CData.empty())
  370. {
  371. this->Rev.Log.assign(&this->CData[0], this->CData.size());
  372. }
  373. this->CData.clear();
  374. }
  375. virtual void ReportError(int, int, const char* msg)
  376. {
  377. this->SVN->Log << "Error parsing svn log xml: " << msg << "\n";
  378. }
  379. };
  380. //----------------------------------------------------------------------------
  381. void cmCTestSVN::LoadRevisions()
  382. {
  383. // Get revisions for all the external repositories
  384. std::list<SVNInfo>::iterator itbeg = this->Repositories.begin();
  385. std::list<SVNInfo>::iterator itend = this->Repositories.end();
  386. for( ; itbeg != itend ; itbeg++)
  387. {
  388. SVNInfo& svninfo = *itbeg;
  389. LoadRevisions(svninfo);
  390. }
  391. }
  392. //----------------------------------------------------------------------------
  393. void cmCTestSVN::LoadRevisions(SVNInfo &svninfo)
  394. {
  395. // We are interested in every revision included in the update.
  396. std::string revs;
  397. if(atoi(svninfo.OldRevision.c_str()) < atoi(svninfo.NewRevision.c_str()))
  398. {
  399. revs = "-r" + svninfo.OldRevision + ":" + svninfo.NewRevision;
  400. }
  401. else
  402. {
  403. revs = "-r" + svninfo.NewRevision;
  404. }
  405. // Run "svn log" to get all global revisions of interest.
  406. std::vector<const char*> svn_log;
  407. svn_log.push_back("log");
  408. svn_log.push_back("--xml");
  409. svn_log.push_back("-v");
  410. svn_log.push_back(revs.c_str());
  411. svn_log.push_back(svninfo.LocalPath.c_str());
  412. LogParser out(this, "log-out> ", svninfo);
  413. OutputLogger err(this->Log, "log-err> ");
  414. this->RunSVNCommand(svn_log, &out, &err);
  415. }
  416. //----------------------------------------------------------------------------
  417. void cmCTestSVN::DoRevisionSVN(Revision const& revision,
  418. std::vector<Change> const& changes)
  419. {
  420. // Guess the base checkout path from the changes if necessary.
  421. if(this->RootInfo->Base.empty() && !changes.empty())
  422. {
  423. this->GuessBase(*this->RootInfo, changes);
  424. }
  425. // Ignore changes in the old revision for external repositories
  426. if(revision.Rev == revision.SVNInfo->OldRevision
  427. && revision.SVNInfo->LocalPath != "")
  428. {
  429. return;
  430. }
  431. this->cmCTestGlobalVC::DoRevision(revision, changes);
  432. }
  433. //----------------------------------------------------------------------------
  434. class cmCTestSVN::StatusParser: public cmCTestVC::LineParser
  435. {
  436. public:
  437. StatusParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  438. {
  439. this->SetLog(&svn->Log, prefix);
  440. this->RegexStatus.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$");
  441. }
  442. private:
  443. cmCTestSVN* SVN;
  444. cmsys::RegularExpression RegexStatus;
  445. bool ProcessLine()
  446. {
  447. if(this->RegexStatus.find(this->Line))
  448. {
  449. this->DoPath(this->RegexStatus.match(1)[0],
  450. this->RegexStatus.match(2)[0],
  451. this->RegexStatus.match(3));
  452. }
  453. return true;
  454. }
  455. void DoPath(char path_status, char prop_status, std::string const& path)
  456. {
  457. char status = (path_status != ' ')? path_status : prop_status;
  458. // See "svn help status".
  459. switch(status)
  460. {
  461. case 'M': case '!': case 'A': case 'D': case 'R':
  462. this->SVN->DoModification(PathModified, path);
  463. break;
  464. case 'C': case '~':
  465. this->SVN->DoModification(PathConflicting, path);
  466. break;
  467. case 'X': case 'I': case '?': case ' ': default:
  468. break;
  469. }
  470. }
  471. };
  472. //----------------------------------------------------------------------------
  473. void cmCTestSVN::LoadModifications()
  474. {
  475. // Run "svn status" which reports local modifications.
  476. std::vector<const char*> svn_status;
  477. svn_status.push_back("status");
  478. StatusParser out(this, "status-out> ");
  479. OutputLogger err(this->Log, "status-err> ");
  480. this->RunSVNCommand(svn_status, &out, &err);
  481. }
  482. //----------------------------------------------------------------------------
  483. void cmCTestSVN::WriteXMLGlobal(std::ostream& xml)
  484. {
  485. this->cmCTestGlobalVC::WriteXMLGlobal(xml);
  486. xml << "\t<SVNPath>" << this->RootInfo->Base << "</SVNPath>\n";
  487. }
  488. //----------------------------------------------------------------------------
  489. class cmCTestSVN::ExternalParser: public cmCTestVC::LineParser
  490. {
  491. public:
  492. ExternalParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  493. {
  494. this->SetLog(&svn->Log, prefix);
  495. this->RegexExternal.compile("^X..... +(.+)$");
  496. }
  497. private:
  498. cmCTestSVN* SVN;
  499. cmsys::RegularExpression RegexExternal;
  500. bool ProcessLine()
  501. {
  502. if(this->RegexExternal.find(this->Line))
  503. {
  504. this->DoPath(this->RegexExternal.match(1));
  505. }
  506. return true;
  507. }
  508. void DoPath(std::string const& path)
  509. {
  510. // Get local path relative to the source directory
  511. std::string local_path;
  512. if(path.size() > this->SVN->SourceDirectory.size() &&
  513. strncmp(path.c_str(), this->SVN->SourceDirectory.c_str(),
  514. this->SVN->SourceDirectory.size()) == 0)
  515. {
  516. local_path = path.c_str() + this->SVN->SourceDirectory.size() + 1;
  517. }
  518. else
  519. {
  520. local_path = path;
  521. }
  522. this->SVN->Repositories.push_back( SVNInfo(local_path.c_str()) );
  523. }
  524. };
  525. //----------------------------------------------------------------------------
  526. void cmCTestSVN::LoadExternals()
  527. {
  528. // Run "svn status" to get the list of external repositories
  529. std::vector<const char*> svn_status;
  530. svn_status.push_back("status");
  531. ExternalParser out(this, "external-out> ");
  532. OutputLogger err(this->Log, "external-err> ");
  533. this->RunSVNCommand(svn_status, &out, &err);
  534. }
  535. //----------------------------------------------------------------------------
  536. std::string cmCTestSVN::SVNInfo::BuildLocalPath(std::string const& path) const
  537. {
  538. std::string local_path;
  539. // Add local path prefix if not empty
  540. if (!this->LocalPath.empty())
  541. {
  542. local_path += this->LocalPath;
  543. local_path += "/";
  544. }
  545. // Add path with base prefix removed
  546. if(path.size() > this->Base.size() &&
  547. strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0)
  548. {
  549. local_path += (path.c_str() + this->Base.size());
  550. }
  551. else
  552. {
  553. local_path += path;
  554. }
  555. return local_path;
  556. }