/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2009 Kitware, Inc. Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmCTestCVS.h" #include "cmCTest.h" #include "cmSystemTools.h" #include "cmXMLSafe.h" #include //---------------------------------------------------------------------------- cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log) { } //---------------------------------------------------------------------------- cmCTestCVS::~cmCTestCVS() { } //---------------------------------------------------------------------------- class cmCTestCVS::UpdateParser: public cmCTestVC::LineParser { public: UpdateParser(cmCTestCVS* cvs, const char* prefix): CVS(cvs) { this->SetLog(&cvs->Log, prefix); // See "man cvs", section "update output". this->RegexFileUpdated.compile("^([UP]) *(.*)"); this->RegexFileModified.compile("^([MRA]) *(.*)"); this->RegexFileConflicting.compile("^([C]) *(.*)"); this->RegexFileRemoved1.compile( "cvs[^ ]* update: `?([^']*)'? is no longer in the repository"); this->RegexFileRemoved2.compile( "cvs[^ ]* update: " "warning: `?([^']*)'? is not \\(any longer\\) pertinent"); } private: cmCTestCVS* CVS; cmsys::RegularExpression RegexFileUpdated; cmsys::RegularExpression RegexFileModified; cmsys::RegularExpression RegexFileConflicting; cmsys::RegularExpression RegexFileRemoved1; cmsys::RegularExpression RegexFileRemoved2; virtual bool ProcessLine() { if(this->RegexFileUpdated.find(this->Line)) { this->DoFile(PathUpdated, this->RegexFileUpdated.match(2)); } else if(this->RegexFileModified.find(this->Line)) { this->DoFile(PathModified, this->RegexFileModified.match(2)); } else if(this->RegexFileConflicting.find(this->Line)) { this->DoFile(PathConflicting, this->RegexFileConflicting.match(2)); } else if(this->RegexFileRemoved1.find(this->Line)) { this->DoFile(PathUpdated, this->RegexFileRemoved1.match(1)); } else if(this->RegexFileRemoved2.find(this->Line)) { this->DoFile(PathUpdated, this->RegexFileRemoved2.match(1)); } return true; } void DoFile(PathStatus status, std::string const& file) { std::string dir = cmSystemTools::GetFilenamePath(file); std::string name = cmSystemTools::GetFilenameName(file); this->CVS->Dirs[dir][name] = status; } }; //---------------------------------------------------------------------------- bool cmCTestCVS::UpdateImpl() { // Get user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); if(opts.empty()) { opts = this->CTest->GetCTestConfiguration("CVSUpdateOptions"); if(opts.empty()) { opts = "-dP"; } } std::vector args = cmSystemTools::ParseArguments(opts.c_str()); // Specify the start time for nightly testing. if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) { args.push_back("-D" + this->GetNightlyTime() + " UTC"); } // Run "cvs update" to update the work tree. std::vector cvs_update; cvs_update.push_back(this->CommandLineTool.c_str()); cvs_update.push_back("-z3"); cvs_update.push_back("update"); for(std::vector::const_iterator ai = args.begin(); ai != args.end(); ++ai) { cvs_update.push_back(ai->c_str()); } cvs_update.push_back(0); UpdateParser out(this, "up-out> "); UpdateParser err(this, "up-err> "); return this->RunUpdateCommand(&cvs_update[0], &out, &err); } //---------------------------------------------------------------------------- class cmCTestCVS::LogParser: public cmCTestVC::LineParser { public: typedef cmCTestCVS::Revision Revision; LogParser(cmCTestCVS* cvs, const char* prefix, std::vector& revs): CVS(cvs), Revisions(revs), Section(SectionHeader) { this->SetLog(&cvs->Log, prefix), this->RegexRevision.compile("^revision +([^ ]*) *$"); this->RegexBranches.compile("^branches: .*$"); this->RegexPerson.compile("^date: +([^;]+); +author: +([^;]+);"); } private: cmCTestCVS* CVS; std::vector& Revisions; cmsys::RegularExpression RegexRevision; cmsys::RegularExpression RegexBranches; cmsys::RegularExpression RegexPerson; enum SectionType { SectionHeader, SectionRevisions, SectionEnd }; SectionType Section; Revision Rev; virtual bool ProcessLine() { if(this->Line == ("=======================================" "======================================")) { // This line ends the revision list. if(this->Section == SectionRevisions) { this->FinishRevision(); } this->Section = SectionEnd; } else if(this->Line == "----------------------------") { // This line divides revisions from the header and each other. if(this->Section == SectionHeader) { this->Section = SectionRevisions; } else if(this->Section == SectionRevisions) { this->FinishRevision(); } } else if(this->Section == SectionRevisions) { if(!this->Rev.Log.empty()) { // Continue the existing log. this->Rev.Log += this->Line; this->Rev.Log += "\n"; } else if(this->Rev.Rev.empty() && this->RegexRevision.find(this->Line)) { this->Rev.Rev = this->RegexRevision.match(1); } else if(this->Rev.Date.empty() && this->RegexPerson.find(this->Line)) { this->Rev.Date = this->RegexPerson.match(1); this->Rev.Author = this->RegexPerson.match(2); } else if(!this->RegexBranches.find(this->Line)) { // Start the log. this->Rev.Log += this->Line; this->Rev.Log += "\n"; } } return this->Section != SectionEnd; } void FinishRevision() { if(!this->Rev.Rev.empty()) { // Record this revision. this->CVS->Log << "Found revision " << this->Rev.Rev << "\n" << " author = " << this->Rev.Author << "\n" << " date = " << this->Rev.Date << "\n"; this->Revisions.push_back(this->Rev); // We only need two revisions. if(this->Revisions.size() >= 2) { this->Section = SectionEnd; } } this->Rev = Revision(); } }; //---------------------------------------------------------------------------- std::string cmCTestCVS::ComputeBranchFlag(std::string const& dir) { // Compute the tag file location for this directory. std::string tagFile = this->SourceDirectory; if(!dir.empty()) { tagFile += "/"; tagFile += dir; } tagFile += "/CVS/Tag"; // Lookup the branch in the tag file, if any. std::string tagLine; std::ifstream tagStream(tagFile.c_str()); if(tagStream && cmSystemTools::GetLineFromStream(tagStream, tagLine) && tagLine.size() > 1 && tagLine[0] == 'T') { // Use the branch specified in the tag file. std::string flag = "-r"; flag += tagLine.substr(1); return flag; } else { // Use the default branch. return "-b"; } } //---------------------------------------------------------------------------- void cmCTestCVS::LoadRevisions(std::string const& file, const char* branchFlag, std::vector& revisions) { cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); // Run "cvs log" to get revisions of this file on this branch. const char* cvs = this->CommandLineTool.c_str(); const char* cvs_log[] = {cvs, "log", "-N", branchFlag, file.c_str(), 0}; LogParser out(this, "log-out> ", revisions); OutputLogger err(this->Log, "log-err> "); this->RunChild(cvs_log, &out, &err); } //---------------------------------------------------------------------------- void cmCTestCVS::WriteXMLDirectory(std::ostream& xml, std::string const& path, Directory const& dir) { const char* slash = path.empty()? "":"/"; xml << "\t\n" << "\t\t" << cmXMLSafe(path) << "\n"; // Lookup the branch checked out in the working tree. std::string branchFlag = this->ComputeBranchFlag(path); // Load revisions and write an entry for each file in this directory. std::vector revisions; for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) { std::string full = path + slash + fi->first; // Load two real or unknown revisions. revisions.clear(); if(fi->second != PathUpdated) { // For local modifications the current rev is unknown and the // prior rev is the latest from cvs. revisions.push_back(this->Unknown); } this->LoadRevisions(full, branchFlag.c_str(), revisions); revisions.resize(2, this->Unknown); // Write the entry for this file with these revisions. File f(fi->second, &revisions[0], &revisions[1]); this->WriteXMLEntry(xml, path, fi->first, full, f); } xml << "\t\n"; } //---------------------------------------------------------------------------- bool cmCTestCVS::WriteXMLUpdates(std::ostream& xml) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Gathering version information (one . per updated file):\n" " " << std::flush); for(std::map::const_iterator di = this->Dirs.begin(); di != this->Dirs.end(); ++di) { this->WriteXMLDirectory(xml, di->first, di->second); } cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); return true; }