cmCTestSVN.cxx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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. const char* svn = this->CommandLineTool.c_str();
  34. const char* svn_cleanup[] = {svn, "cleanup", 0};
  35. OutputLogger out(this->Log, "cleanup-out> ");
  36. OutputLogger err(this->Log, "cleanup-err> ");
  37. this->RunChild(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. const char* svn = this->CommandLineTool.c_str();
  99. const char* svn_info[] = {svn, "info", svninfo.LocalPath.c_str(), 0};
  100. std::string rev;
  101. InfoParser out(this, "info-out> ", rev, svninfo);
  102. OutputLogger err(this->Log, "info-err> ");
  103. this->RunChild(svn_info, &out, &err);
  104. return rev;
  105. }
  106. //----------------------------------------------------------------------------
  107. void cmCTestSVN::NoteOldRevision()
  108. {
  109. // Info for root repository
  110. this->Repositories.push_back( SVNInfo("") );
  111. this->RootInfo = &(this->Repositories.back());
  112. // Get info for the root repositiry
  113. SVNInfo& svninfo = *RootInfo;
  114. svninfo.OldRevision = this->LoadInfo(svninfo);
  115. this->Log << "Revision for repository '" << svninfo.LocalPath
  116. << "' before update: " << svninfo.OldRevision << "\n";
  117. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  118. " Old revision of external repository '"
  119. << svninfo.LocalPath << "' is: "
  120. << svninfo.OldRevision << "\n");
  121. // Set the global old revision to the one of the root
  122. this->OldRevision = this->RootInfo->OldRevision;
  123. this->PriorRev.Rev = this->OldRevision;
  124. }
  125. //----------------------------------------------------------------------------
  126. void cmCTestSVN::NoteNewRevision()
  127. {
  128. // Get info for the root repository
  129. SVNInfo& svninfo = *RootInfo;
  130. svninfo.NewRevision = this->LoadInfo(svninfo);
  131. this->Log << "Revision for repository '" << svninfo.LocalPath
  132. << "' after update: " << svninfo.NewRevision << "\n";
  133. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  134. " New revision of external repository '"
  135. << svninfo.LocalPath << "' is: "
  136. << svninfo.NewRevision << "\n");
  137. // svninfo.Root = ""; // uncomment to test GuessBase
  138. this->Log << "Repository '" << svninfo.LocalPath
  139. << "' URL = " << svninfo.URL << "\n";
  140. this->Log << "Repository '" << svninfo.LocalPath
  141. << "' Root = " << svninfo.Root << "\n";
  142. // Compute the base path the working tree has checked out under
  143. // the repository root.
  144. if(!svninfo.Root.empty()
  145. && cmCTestSVNPathStarts(svninfo.URL, svninfo.Root))
  146. {
  147. svninfo.Base = cmCTest::DecodeURL(
  148. svninfo.URL.substr(svninfo.Root.size()));
  149. svninfo.Base += "/";
  150. }
  151. this->Log << "Repository '" << svninfo.LocalPath
  152. << "' Base = " << svninfo.Base << "\n";
  153. // Set the global new revision to the one of the root
  154. this->NewRevision = this->RootInfo->NewRevision;
  155. }
  156. //----------------------------------------------------------------------------
  157. void cmCTestSVN::GuessBase(SVNInfo& svninfo,
  158. std::vector<Change> const& changes)
  159. {
  160. // Subversion did not give us a good repository root so we need to
  161. // guess the base path from the URL and the paths in a revision with
  162. // changes under it.
  163. // Consider each possible URL suffix from longest to shortest.
  164. for(std::string::size_type slash = svninfo.URL.find('/');
  165. svninfo.Base.empty() && slash != std::string::npos;
  166. slash = svninfo.URL.find('/', slash+1))
  167. {
  168. // If the URL suffix is a prefix of at least one path then it is the base.
  169. std::string base = cmCTest::DecodeURL(svninfo.URL.substr(slash));
  170. for(std::vector<Change>::const_iterator ci = changes.begin();
  171. svninfo.Base.empty() && ci != changes.end(); ++ci)
  172. {
  173. if(cmCTestSVNPathStarts(ci->Path, base))
  174. {
  175. svninfo.Base = base;
  176. }
  177. }
  178. }
  179. // We always append a slash so that we know paths beginning in the
  180. // base lie under its path. If no base was found then the working
  181. // tree must be a checkout of the entire repo and this will match
  182. // the leading slash in all paths.
  183. svninfo.Base += "/";
  184. this->Log << "Guessed Base = " << svninfo.Base << "\n";
  185. }
  186. //----------------------------------------------------------------------------
  187. class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser
  188. {
  189. public:
  190. UpdateParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  191. {
  192. this->SetLog(&svn->Log, prefix);
  193. this->RegexUpdate.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$");
  194. }
  195. private:
  196. cmCTestSVN* SVN;
  197. cmsys::RegularExpression RegexUpdate;
  198. bool ProcessLine()
  199. {
  200. if(this->RegexUpdate.find(this->Line))
  201. {
  202. this->DoPath(this->RegexUpdate.match(1)[0],
  203. this->RegexUpdate.match(2)[0],
  204. this->RegexUpdate.match(3));
  205. }
  206. return true;
  207. }
  208. void DoPath(char path_status, char prop_status, std::string const& path)
  209. {
  210. char status = (path_status != ' ')? path_status : prop_status;
  211. std::string dir = cmSystemTools::GetFilenamePath(path);
  212. std::string name = cmSystemTools::GetFilenameName(path);
  213. // See "svn help update".
  214. switch(status)
  215. {
  216. case 'G':
  217. this->SVN->Dirs[dir][name].Status = PathModified;
  218. break;
  219. case 'C':
  220. this->SVN->Dirs[dir][name].Status = PathConflicting;
  221. break;
  222. case 'A': case 'D': case 'U':
  223. this->SVN->Dirs[dir][name].Status = PathUpdated;
  224. break;
  225. case 'E': // TODO?
  226. case '?': case ' ': default:
  227. break;
  228. }
  229. }
  230. };
  231. //----------------------------------------------------------------------------
  232. bool cmCTestSVN::UpdateImpl()
  233. {
  234. // Get user-specified update options.
  235. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  236. if(opts.empty())
  237. {
  238. opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions");
  239. }
  240. std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
  241. // Specify the start time for nightly testing.
  242. if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
  243. {
  244. args.push_back("-r{" + this->GetNightlyTime() + " +0000}");
  245. }
  246. std::vector<char const*> svn_update;
  247. svn_update.push_back(this->CommandLineTool.c_str());
  248. svn_update.push_back("update");
  249. svn_update.push_back("--non-interactive");
  250. for(std::vector<cmStdString>::const_iterator ai = args.begin();
  251. ai != args.end(); ++ai)
  252. {
  253. svn_update.push_back(ai->c_str());
  254. }
  255. svn_update.push_back(0);
  256. UpdateParser out(this, "up-out> ");
  257. OutputLogger err(this->Log, "up-err> ");
  258. return this->RunUpdateCommand(&svn_update[0], &out, &err);
  259. }
  260. //----------------------------------------------------------------------------
  261. class cmCTestSVN::LogParser: public cmCTestVC::OutputLogger,
  262. private cmXMLParser
  263. {
  264. public:
  265. LogParser(cmCTestSVN* svn, const char* prefix, SVNInfo& svninfo):
  266. OutputLogger(svn->Log, prefix), SVN(svn), SVNRepo(svninfo)
  267. { this->InitializeParser(); }
  268. ~LogParser() { this->CleanupParser(); }
  269. private:
  270. cmCTestSVN* SVN;
  271. cmCTestSVN::SVNInfo& SVNRepo;
  272. typedef cmCTestSVN::Revision Revision;
  273. typedef cmCTestSVN::Change Change;
  274. Revision Rev;
  275. std::vector<Change> Changes;
  276. Change CurChange;
  277. std::vector<char> CData;
  278. virtual bool ProcessChunk(const char* data, int length)
  279. {
  280. this->OutputLogger::ProcessChunk(data, length);
  281. this->ParseChunk(data, length);
  282. return true;
  283. }
  284. virtual void StartElement(const char* name, const char** atts)
  285. {
  286. this->CData.clear();
  287. if(strcmp(name, "logentry") == 0)
  288. {
  289. this->Rev = Revision();
  290. this->Rev.SVNInfo = &SVNRepo;
  291. if(const char* rev = this->FindAttribute(atts, "revision"))
  292. {
  293. this->Rev.Rev = rev;
  294. }
  295. this->Changes.clear();
  296. }
  297. else if(strcmp(name, "path") == 0)
  298. {
  299. this->CurChange = Change();
  300. if(const char* action = this->FindAttribute(atts, "action"))
  301. {
  302. this->CurChange.Action = action[0];
  303. }
  304. }
  305. }
  306. virtual void CharacterDataHandler(const char* data, int length)
  307. {
  308. this->CData.insert(this->CData.end(), data, data+length);
  309. }
  310. virtual void EndElement(const char* name)
  311. {
  312. if(strcmp(name, "logentry") == 0)
  313. {
  314. this->SVN->DoRevisionSVN(this->Rev, this->Changes);
  315. }
  316. else if(strcmp(name, "path") == 0 && !this->CData.empty())
  317. {
  318. std::string orig_path(&this->CData[0], this->CData.size());
  319. std::string new_path = SVNRepo.BuildLocalPath( orig_path );
  320. this->CurChange.Path.assign(new_path);
  321. this->Changes.push_back(this->CurChange);
  322. }
  323. else if(strcmp(name, "author") == 0 && !this->CData.empty())
  324. {
  325. this->Rev.Author.assign(&this->CData[0], this->CData.size());
  326. }
  327. else if(strcmp(name, "date") == 0 && !this->CData.empty())
  328. {
  329. this->Rev.Date.assign(&this->CData[0], this->CData.size());
  330. }
  331. else if(strcmp(name, "msg") == 0 && !this->CData.empty())
  332. {
  333. this->Rev.Log.assign(&this->CData[0], this->CData.size());
  334. }
  335. this->CData.clear();
  336. }
  337. virtual void ReportError(int, int, const char* msg)
  338. {
  339. this->SVN->Log << "Error parsing svn log xml: " << msg << "\n";
  340. }
  341. };
  342. //----------------------------------------------------------------------------
  343. void cmCTestSVN::LoadRevisions()
  344. {
  345. // Get revision of the root repository
  346. SVNInfo& svninfo = *RootInfo;
  347. LoadRevisions(svninfo);
  348. }
  349. //----------------------------------------------------------------------------
  350. void cmCTestSVN::LoadRevisions(SVNInfo &svninfo)
  351. {
  352. // We are interested in every revision included in the update.
  353. std::string revs;
  354. if(atoi(svninfo.OldRevision.c_str()) < atoi(svninfo.NewRevision.c_str()))
  355. {
  356. revs = "-r" + svninfo.OldRevision + ":" + svninfo.NewRevision;
  357. }
  358. else
  359. {
  360. revs = "-r" + svninfo.NewRevision;
  361. }
  362. // Run "svn log" to get all global revisions of interest.
  363. const char* svn = this->CommandLineTool.c_str();
  364. const char* svn_log[] = {svn, "log", "--xml", "-v", revs.c_str(),
  365. svninfo.LocalPath.c_str(), 0};
  366. {
  367. LogParser out(this, "log-out> ", svninfo);
  368. OutputLogger err(this->Log, "log-err> ");
  369. this->RunChild(svn_log, &out, &err);
  370. }
  371. }
  372. //----------------------------------------------------------------------------
  373. void cmCTestSVN::DoRevisionSVN(Revision const& revision,
  374. std::vector<Change> const& changes)
  375. {
  376. // Guess the base checkout path from the changes if necessary.
  377. if(this->RootInfo->Base.empty() && !changes.empty())
  378. {
  379. this->GuessBase(*this->RootInfo, changes);
  380. }
  381. this->cmCTestGlobalVC::DoRevision(revision, changes);
  382. }
  383. //----------------------------------------------------------------------------
  384. class cmCTestSVN::StatusParser: public cmCTestVC::LineParser
  385. {
  386. public:
  387. StatusParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
  388. {
  389. this->SetLog(&svn->Log, prefix);
  390. this->RegexStatus.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$");
  391. }
  392. private:
  393. cmCTestSVN* SVN;
  394. cmsys::RegularExpression RegexStatus;
  395. bool ProcessLine()
  396. {
  397. if(this->RegexStatus.find(this->Line))
  398. {
  399. this->DoPath(this->RegexStatus.match(1)[0],
  400. this->RegexStatus.match(2)[0],
  401. this->RegexStatus.match(3));
  402. }
  403. return true;
  404. }
  405. void DoPath(char path_status, char prop_status, std::string const& path)
  406. {
  407. char status = (path_status != ' ')? path_status : prop_status;
  408. // See "svn help status".
  409. switch(status)
  410. {
  411. case 'M': case '!': case 'A': case 'D': case 'R':
  412. this->SVN->DoModification(PathModified, path);
  413. break;
  414. case 'C': case '~':
  415. this->SVN->DoModification(PathConflicting, path);
  416. break;
  417. case 'X': case 'I': case '?': case ' ': default:
  418. break;
  419. }
  420. }
  421. };
  422. //----------------------------------------------------------------------------
  423. void cmCTestSVN::LoadModifications()
  424. {
  425. // Run "svn status" which reports local modifications.
  426. const char* svn = this->CommandLineTool.c_str();
  427. const char* svn_status[] = {svn, "status", "--non-interactive", 0};
  428. StatusParser out(this, "status-out> ");
  429. OutputLogger err(this->Log, "status-err> ");
  430. this->RunChild(svn_status, &out, &err);
  431. }
  432. //----------------------------------------------------------------------------
  433. void cmCTestSVN::WriteXMLGlobal(std::ostream& xml)
  434. {
  435. this->cmCTestGlobalVC::WriteXMLGlobal(xml);
  436. xml << "\t<SVNPath>" << this->RootInfo->Base << "</SVNPath>\n";
  437. }
  438. //----------------------------------------------------------------------------
  439. std::string cmCTestSVN::SVNInfo::BuildLocalPath(std::string const& path) const
  440. {
  441. std::string local_path;
  442. // Add local path prefix if not empty
  443. if (!this->LocalPath.empty())
  444. {
  445. local_path += this->LocalPath;
  446. local_path += "/";
  447. }
  448. // Add path with base prefix removed
  449. if(path.size() > this->Base.size() &&
  450. strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0)
  451. {
  452. local_path += (path.c_str() + this->Base.size());
  453. }
  454. else
  455. {
  456. local_path += path;
  457. }
  458. return local_path;
  459. }