| /*============================================================================ |
| 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 <cmsys/RegularExpression.hxx> |
| #include <cmsys/FStream.hxx> |
| |
| //---------------------------------------------------------------------------- |
| 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<cmStdString> 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<char const*> cvs_update; |
| cvs_update.push_back(this->CommandLineTool.c_str()); |
| cvs_update.push_back("-z3"); |
| cvs_update.push_back("update"); |
| for(std::vector<cmStdString>::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<Revision>& 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<Revision>& 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; |
| cmsys::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<Revision>& 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<Directory>\n" |
| << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\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<Revision> 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</Directory>\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<cmStdString, Directory>::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; |
| } |