cmCTestCVS.cxx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCTestCVS.h"
  4. #include <utility>
  5. #include <cm/string_view>
  6. #include "cmsys/FStream.hxx"
  7. #include "cmsys/RegularExpression.hxx"
  8. #include "cmCTest.h"
  9. #include "cmProcessTools.h"
  10. #include "cmStringAlgorithms.h"
  11. #include "cmSystemTools.h"
  12. #include "cmXMLWriter.h"
  13. cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log)
  14. : cmCTestVC(ct, log)
  15. {
  16. }
  17. cmCTestCVS::~cmCTestCVS() = default;
  18. class cmCTestCVS::UpdateParser : public cmCTestVC::LineParser
  19. {
  20. public:
  21. UpdateParser(cmCTestCVS* cvs, const char* prefix)
  22. : CVS(cvs)
  23. {
  24. this->SetLog(&cvs->Log, prefix);
  25. // See "man cvs", section "update output".
  26. this->RegexFileUpdated.compile("^([UP]) *(.*)");
  27. this->RegexFileModified.compile("^([MRA]) *(.*)");
  28. this->RegexFileConflicting.compile("^([C]) *(.*)");
  29. this->RegexFileRemoved1.compile(
  30. "cvs[^ ]* update: `?([^']*)'? is no longer in the repository");
  31. this->RegexFileRemoved2.compile(
  32. "cvs[^ ]* update: "
  33. "warning: `?([^']*)'? is not \\(any longer\\) pertinent");
  34. }
  35. private:
  36. cmCTestCVS* CVS;
  37. cmsys::RegularExpression RegexFileUpdated;
  38. cmsys::RegularExpression RegexFileModified;
  39. cmsys::RegularExpression RegexFileConflicting;
  40. cmsys::RegularExpression RegexFileRemoved1;
  41. cmsys::RegularExpression RegexFileRemoved2;
  42. bool ProcessLine() override
  43. {
  44. if (this->RegexFileUpdated.find(this->Line)) {
  45. this->DoFile(PathUpdated, this->RegexFileUpdated.match(2));
  46. } else if (this->RegexFileModified.find(this->Line)) {
  47. this->DoFile(PathModified, this->RegexFileModified.match(2));
  48. } else if (this->RegexFileConflicting.find(this->Line)) {
  49. this->DoFile(PathConflicting, this->RegexFileConflicting.match(2));
  50. } else if (this->RegexFileRemoved1.find(this->Line)) {
  51. this->DoFile(PathUpdated, this->RegexFileRemoved1.match(1));
  52. } else if (this->RegexFileRemoved2.find(this->Line)) {
  53. this->DoFile(PathUpdated, this->RegexFileRemoved2.match(1));
  54. }
  55. return true;
  56. }
  57. void DoFile(PathStatus status, std::string const& file)
  58. {
  59. std::string dir = cmSystemTools::GetFilenamePath(file);
  60. std::string name = cmSystemTools::GetFilenameName(file);
  61. this->CVS->Dirs[dir][name] = status;
  62. }
  63. };
  64. bool cmCTestCVS::UpdateImpl()
  65. {
  66. // Get user-specified update options.
  67. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  68. if (opts.empty()) {
  69. opts = this->CTest->GetCTestConfiguration("CVSUpdateOptions");
  70. if (opts.empty()) {
  71. opts = "-dP";
  72. }
  73. }
  74. std::vector<std::string> args = cmSystemTools::ParseArguments(opts);
  75. // Specify the start time for nightly testing.
  76. if (this->CTest->GetTestModel() == cmCTest::NIGHTLY) {
  77. args.push_back("-D" + this->GetNightlyTime() + " UTC");
  78. }
  79. // Run "cvs update" to update the work tree.
  80. std::vector<char const*> cvs_update;
  81. cvs_update.push_back(this->CommandLineTool.c_str());
  82. cvs_update.push_back("-z3");
  83. cvs_update.push_back("update");
  84. for (std::string const& arg : args) {
  85. cvs_update.push_back(arg.c_str());
  86. }
  87. cvs_update.push_back(nullptr);
  88. UpdateParser out(this, "up-out> ");
  89. UpdateParser err(this, "up-err> ");
  90. return this->RunUpdateCommand(&cvs_update[0], &out, &err);
  91. }
  92. class cmCTestCVS::LogParser : public cmCTestVC::LineParser
  93. {
  94. public:
  95. using Revision = cmCTestCVS::Revision;
  96. LogParser(cmCTestCVS* cvs, const char* prefix, std::vector<Revision>& revs)
  97. : CVS(cvs)
  98. , Revisions(revs)
  99. , Section(SectionHeader)
  100. {
  101. this->SetLog(&cvs->Log, prefix);
  102. this->RegexRevision.compile("^revision +([^ ]*) *$");
  103. this->RegexBranches.compile("^branches: .*$");
  104. this->RegexPerson.compile("^date: +([^;]+); +author: +([^;]+);");
  105. }
  106. private:
  107. cmCTestCVS* CVS;
  108. std::vector<Revision>& Revisions;
  109. cmsys::RegularExpression RegexRevision;
  110. cmsys::RegularExpression RegexBranches;
  111. cmsys::RegularExpression RegexPerson;
  112. enum SectionType
  113. {
  114. SectionHeader,
  115. SectionRevisions,
  116. SectionEnd
  117. };
  118. SectionType Section;
  119. Revision Rev;
  120. bool ProcessLine() override
  121. {
  122. if (this->Line ==
  123. ("======================================="
  124. "======================================")) {
  125. // This line ends the revision list.
  126. if (this->Section == SectionRevisions) {
  127. this->FinishRevision();
  128. }
  129. this->Section = SectionEnd;
  130. } else if (this->Line == "----------------------------") {
  131. // This line divides revisions from the header and each other.
  132. if (this->Section == SectionHeader) {
  133. this->Section = SectionRevisions;
  134. } else if (this->Section == SectionRevisions) {
  135. this->FinishRevision();
  136. }
  137. } else if (this->Section == SectionRevisions) {
  138. // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
  139. // NOLINTNEXTLINE(bugprone-branch-clone)
  140. if (!this->Rev.Log.empty()) {
  141. // Continue the existing log.
  142. this->Rev.Log += this->Line;
  143. this->Rev.Log += '\n';
  144. } else if (this->Rev.Rev.empty() &&
  145. this->RegexRevision.find(this->Line)) {
  146. this->Rev.Rev = this->RegexRevision.match(1);
  147. } else if (this->Rev.Date.empty() &&
  148. this->RegexPerson.find(this->Line)) {
  149. this->Rev.Date = this->RegexPerson.match(1);
  150. this->Rev.Author = this->RegexPerson.match(2);
  151. } else if (!this->RegexBranches.find(this->Line)) {
  152. // Start the log.
  153. this->Rev.Log += this->Line;
  154. this->Rev.Log += '\n';
  155. }
  156. }
  157. return this->Section != SectionEnd;
  158. }
  159. void FinishRevision()
  160. {
  161. if (!this->Rev.Rev.empty()) {
  162. // Record this revision.
  163. /* clang-format off */
  164. this->CVS->Log << "Found revision " << this->Rev.Rev << "\n"
  165. << " author = " << this->Rev.Author << "\n"
  166. << " date = " << this->Rev.Date << "\n";
  167. /* clang-format on */
  168. this->Revisions.push_back(this->Rev);
  169. // We only need two revisions.
  170. if (this->Revisions.size() >= 2) {
  171. this->Section = SectionEnd;
  172. }
  173. }
  174. this->Rev = Revision();
  175. }
  176. };
  177. std::string cmCTestCVS::ComputeBranchFlag(std::string const& dir)
  178. {
  179. // Compute the tag file location for this directory.
  180. std::string tagFile = this->SourceDirectory;
  181. if (!dir.empty()) {
  182. tagFile += "/";
  183. tagFile += dir;
  184. }
  185. tagFile += "/CVS/Tag";
  186. // Lookup the branch in the tag file, if any.
  187. std::string tagLine;
  188. cmsys::ifstream tagStream(tagFile.c_str());
  189. if (tagStream && cmSystemTools::GetLineFromStream(tagStream, tagLine) &&
  190. tagLine.size() > 1 && tagLine[0] == 'T') {
  191. // Use the branch specified in the tag file.
  192. std::string flag = cmStrCat("-r", cm::string_view(tagLine).substr(1));
  193. return flag;
  194. }
  195. // Use the default branch.
  196. return "-b";
  197. }
  198. void cmCTestCVS::LoadRevisions(std::string const& file, const char* branchFlag,
  199. std::vector<Revision>& revisions)
  200. {
  201. cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush);
  202. // Run "cvs log" to get revisions of this file on this branch.
  203. const char* cvs = this->CommandLineTool.c_str();
  204. const char* cvs_log[] = {
  205. cvs, "log", "-N", branchFlag, file.c_str(), nullptr
  206. };
  207. LogParser out(this, "log-out> ", revisions);
  208. OutputLogger err(this->Log, "log-err> ");
  209. this->RunChild(cvs_log, &out, &err);
  210. }
  211. void cmCTestCVS::WriteXMLDirectory(cmXMLWriter& xml, std::string const& path,
  212. Directory const& dir)
  213. {
  214. const char* slash = path.empty() ? "" : "/";
  215. xml.StartElement("Directory");
  216. xml.Element("Name", path);
  217. // Lookup the branch checked out in the working tree.
  218. std::string branchFlag = this->ComputeBranchFlag(path);
  219. // Load revisions and write an entry for each file in this directory.
  220. std::vector<Revision> revisions;
  221. for (auto const& fi : dir) {
  222. std::string full = path + slash + fi.first;
  223. // Load two real or unknown revisions.
  224. revisions.clear();
  225. if (fi.second != PathUpdated) {
  226. // For local modifications the current rev is unknown and the
  227. // prior rev is the latest from cvs.
  228. revisions.push_back(this->Unknown);
  229. }
  230. this->LoadRevisions(full, branchFlag.c_str(), revisions);
  231. revisions.resize(2, this->Unknown);
  232. // Write the entry for this file with these revisions.
  233. File f(fi.second, &revisions[0], &revisions[1]);
  234. this->WriteXMLEntry(xml, path, fi.first, full, f);
  235. }
  236. xml.EndElement(); // Directory
  237. }
  238. bool cmCTestCVS::WriteXMLUpdates(cmXMLWriter& xml)
  239. {
  240. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  241. " Gathering version information (one . per updated file):\n"
  242. " "
  243. << std::flush);
  244. for (auto const& d : this->Dirs) {
  245. this->WriteXMLDirectory(xml, d.first, d.second);
  246. }
  247. cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);
  248. return true;
  249. }