diff options
-rw-r--r-- | Source/CTest/cmCTestCVS.cxx | 296 | ||||
-rw-r--r-- | Source/CTest/cmCTestCVS.h | 21 | ||||
-rw-r--r-- | Source/CTest/cmCTestSVN.cxx | 390 | ||||
-rw-r--r-- | Source/CTest/cmCTestSVN.h | 38 | ||||
-rw-r--r-- | Source/CTest/cmCTestUpdateHandler.cxx | 657 | ||||
-rw-r--r-- | Source/CTest/cmCTestVC.cxx | 82 | ||||
-rw-r--r-- | Source/CTest/cmCTestVC.h | 58 |
7 files changed, 901 insertions, 641 deletions
diff --git a/Source/CTest/cmCTestCVS.cxx b/Source/CTest/cmCTestCVS.cxx index 0f4477e..cfc9bcf 100644 --- a/Source/CTest/cmCTestCVS.cxx +++ b/Source/CTest/cmCTestCVS.cxx @@ -16,6 +16,12 @@ =========================================================================*/ #include "cmCTestCVS.h" +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + //---------------------------------------------------------------------------- cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log) { @@ -25,3 +31,293 @@ 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; + std::ifstream tagStream(tagFile.c_str()); + if(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", "-d<now", 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; +} diff --git a/Source/CTest/cmCTestCVS.h b/Source/CTest/cmCTestCVS.h index f5175c2..c5f87b9 100644 --- a/Source/CTest/cmCTestCVS.h +++ b/Source/CTest/cmCTestCVS.h @@ -30,6 +30,27 @@ public: cmCTestCVS(cmCTest* ctest, std::ostream& log); virtual ~cmCTestCVS(); + +private: + // Implement cmCTestVC internal API. + virtual bool UpdateImpl(); + virtual bool WriteXMLUpdates(std::ostream& xml); + + // Update status for files in each directory. + class Directory: public std::map<cmStdString, PathStatus> {}; + std::map<cmStdString, Directory> Dirs; + + std::string ComputeBranchFlag(std::string const& dir); + void LoadRevisions(std::string const& file, const char* branchFlag, + std::vector<Revision>& revisions); + void WriteXMLDirectory(std::ostream& xml, std::string const& path, + Directory const& dir); + + // Parsing helper classes. + class UpdateParser; + class LogParser; + friend class UpdateParser; + friend class LogParser; }; #endif diff --git a/Source/CTest/cmCTestSVN.cxx b/Source/CTest/cmCTestSVN.cxx index d6b8306..bb0c3ea 100644 --- a/Source/CTest/cmCTestSVN.cxx +++ b/Source/CTest/cmCTestSVN.cxx @@ -17,12 +17,16 @@ #include "cmCTestSVN.h" #include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" #include <cmsys/RegularExpression.hxx> //---------------------------------------------------------------------------- cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log) { + this->PriorRev = this->Unknown; } //---------------------------------------------------------------------------- @@ -114,6 +118,7 @@ void cmCTestSVN::NoteOldRevision() this->Log << "Revision before update: " << this->OldRevision << "\n"; cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; } //---------------------------------------------------------------------------- @@ -124,6 +129,7 @@ void cmCTestSVN::NoteNewRevision() cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " << this->NewRevision << "\n"); + // this->Root = ""; // uncomment to test GuessBase this->Log << "URL = " << this->URL << "\n"; this->Log << "Root = " << this->Root << "\n"; @@ -136,3 +142,387 @@ void cmCTestSVN::NoteNewRevision() } this->Log << "Base = " << this->Base << "\n"; } + +//---------------------------------------------------------------------------- +void cmCTestSVN::GuessBase(std::vector<Change> const& changes) +{ + // Subversion did not give us a good repository root so we need to + // guess the base path from the URL and the paths in a revision with + // changes under it. + + // Consider each possible URL suffix from longest to shortest. + for(std::string::size_type slash = this->URL.find('/'); + this->Base.empty() && slash != std::string::npos; + slash = this->URL.find('/', slash+1)) + { + // If the URL suffix is a prefix of at least one path then it is the base. + std::string base = cmCTest::DecodeURL(this->URL.substr(slash)); + for(std::vector<Change>::const_iterator ci = changes.begin(); + this->Base.empty() && ci != changes.end(); ++ci) + { + if(cmCTestSVNPathStarts(ci->Path, base)) + { + this->Base = base; + } + } + } + + // We always append a slash so that we know paths beginning in the + // base lie under its path. If no base was found then the working + // tree must be a checkout of the entire repo and this will match + // the leading slash in all paths. + this->Base += "/"; + + this->Log << "Guessed Base = " << this->Base << "\n"; +} + +//---------------------------------------------------------------------------- +const char* cmCTestSVN::LocalPath(std::string const& path) +{ + if(path.size() > this->Base.size() && + strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0) + { + // This path lies under the base, so return a relative path. + return path.c_str() + this->Base.size(); + } + else + { + // This path does not lie under the base, so ignore it. + return 0; + } +} + +//---------------------------------------------------------------------------- +class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestSVN* svn, const char* prefix): SVN(svn) + { + this->SetLog(&svn->Log, prefix); + this->RegexUpdate.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$"); + } +private: + cmCTestSVN* SVN; + cmsys::RegularExpression RegexUpdate; + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)); + } + return true; + } + + void DoPath(char path_status, char prop_status, std::string const& path) + { + char status = (path_status != ' ')? path_status : prop_status; + std::string dir = cmSystemTools::GetFilenamePath(path); + std::string name = cmSystemTools::GetFilenameName(path); + // See "svn help update". + switch(status) + { + case 'G': + this->SVN->Dirs[dir][name].Status = PathModified; + break; + case 'C': + this->SVN->Dirs[dir][name].Status = PathConflicting; + break; + case 'A': case 'D': case 'U': + this->SVN->Dirs[dir][name].Status = PathUpdated; + break; + case 'E': // TODO? + case '?': case ' ': default: + break; + } + } +}; + +//---------------------------------------------------------------------------- +bool cmCTestSVN::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions"); + } + 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("-r{" + this->GetNightlyTime() + " +0000}"); + } + + std::vector<char const*> svn_update; + svn_update.push_back(this->CommandLineTool.c_str()); + svn_update.push_back("update"); + svn_update.push_back("--non-interactive"); + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + svn_update.push_back(ai->c_str()); + } + svn_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + return this->RunUpdateCommand(&svn_update[0], &out, &err); +} + +//---------------------------------------------------------------------------- +class cmCTestSVN::LogParser: public OutputLogger, private cmXMLParser +{ +public: + LogParser(cmCTestSVN* svn, const char* prefix): + OutputLogger(svn->Log, prefix), SVN(svn) { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } +private: + cmCTestSVN* SVN; + + typedef cmCTestSVN::Revision Revision; + typedef cmCTestSVN::Change Change; + Revision Rev; + std::vector<Change> Changes; + Change CurChange; + std::vector<char> CData; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "logentry") == 0) + { + this->Rev = Revision(); + if(const char* rev = this->FindAttribute(atts, "revision")) + { + this->Rev.Rev = rev; + } + this->Changes.clear(); + } + else if(strcmp(name, "path") == 0) + { + this->CurChange = Change(); + if(const char* action = this->FindAttribute(atts, "action")) + { + this->CurChange.Action = action[0]; + } + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "logentry") == 0) + { + this->SVN->DoRevision(this->Rev, this->Changes); + } + else if(strcmp(name, "path") == 0 && !this->CData.empty()) + { + this->CurChange.Path.assign(&this->CData[0], this->CData.size()); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "author") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "date") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "msg") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->SVN->Log << "Error parsing svn log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +void cmCTestSVN::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + std::string revs; + if(atoi(this->OldRevision.c_str()) < atoi(this->NewRevision.c_str())) + { + revs = "-r" + this->OldRevision + ":" + this->NewRevision; + } + else + { + revs = "-r" + this->NewRevision; + } + + // Run "svn log" to get all global revisions of interest. + const char* svn = this->CommandLineTool.c_str(); + const char* svn_log[] = {svn, "log", "--xml", "-v", revs.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(svn_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +void cmCTestSVN::DoRevision(Revision const& revision, + std::vector<Change> const& changes) +{ + // Guess the base checkout path from the changes if necessary. + if(this->Base.empty() && !changes.empty()) + { + this->GuessBase(changes); + } + + // Indicate we found a revision. + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + + // Ignore changes in the old revision. + if(revision.Rev == this->OldRevision) + { + this->PriorRev = revision; + return; + } + + // Store the revision. + this->Revisions.push_back(revision); + + // Report this revision. + Revision const& rev = this->Revisions.back(); + this->Log << "Found revision " << rev.Rev << "\n" + << " author = " << rev.Author << "\n" + << " date = " << rev.Date << "\n"; + + // Update information about revisions of the changed files. + for(std::vector<Change>::const_iterator ci = changes.begin(); + ci != changes.end(); ++ci) + { + if(const char* local = this->LocalPath(ci->Path)) + { + std::string dir = cmSystemTools::GetFilenamePath(local); + std::string name = cmSystemTools::GetFilenameName(local); + File& file = this->Dirs[dir][name]; + file.PriorRev = file.Rev? file.Rev : &this->PriorRev; + file.Rev = &rev; + this->Log << " " << ci->Action << " " << local << " " << "\n"; + } + } +} + +//---------------------------------------------------------------------------- +class cmCTestSVN::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestSVN* svn, const char* prefix): SVN(svn) + { + this->SetLog(&svn->Log, prefix); + this->RegexStatus.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$"); + } +private: + cmCTestSVN* SVN; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)); + } + return true; + } + + void DoPath(char path_status, char prop_status, std::string const& path) + { + char status = (path_status != ' ')? path_status : prop_status; + // See "svn help status". + switch(status) + { + case 'M': case '!': case 'A': case 'D': case 'R': case 'X': + this->DoPath(PathModified, path); + break; + case 'C': case '~': + this->DoPath(PathConflicting, path); + break; + case 'I': case '?': case ' ': default: + break; + } + } + + void DoPath(PathStatus status, std::string const& path) + { + std::string dir = cmSystemTools::GetFilenamePath(path); + std::string name = cmSystemTools::GetFilenameName(path); + File& file = this->SVN->Dirs[dir][name]; + file.Status = status; + // For local modifications the current rev is unknown and the + // prior rev is the latest from svn. + if(!file.Rev && !file.PriorRev) + { + file.PriorRev = &this->SVN->PriorRev; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestSVN::LoadModifications() +{ + // Run "svn status" which reports local modifications. + const char* svn = this->CommandLineTool.c_str(); + const char* svn_status[] = {svn, "status", "--non-interactive", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(svn_status, &out, &err); +} + +//---------------------------------------------------------------------------- +void cmCTestSVN::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"; + for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) + { + std::string full = path + slash + fi->first; + this->WriteXMLEntry(xml, path, fi->first, full, fi->second); + } + xml << "\t</Directory>\n"; +} + +//---------------------------------------------------------------------------- +bool cmCTestSVN::WriteXMLUpdates(std::ostream& xml) +{ + this->LoadRevisions(); + this->LoadModifications(); + + for(std::map<cmStdString, Directory>::const_iterator + di = this->Dirs.begin(); di != this->Dirs.end(); ++di) + { + this->WriteXMLDirectory(xml, di->first, di->second); + } + + return true; +} diff --git a/Source/CTest/cmCTestSVN.h b/Source/CTest/cmCTestSVN.h index af14cf0..32b472d 100644 --- a/Source/CTest/cmCTestSVN.h +++ b/Source/CTest/cmCTestSVN.h @@ -31,13 +31,25 @@ public: virtual ~cmCTestSVN(); - int GetOldRevision() { return atoi(this->OldRevision.c_str()); } - int GetNewRevision() { return atoi(this->NewRevision.c_str()); } private: // Implement cmCTestVC internal API. virtual void CleanupImpl(); virtual void NoteOldRevision(); virtual void NoteNewRevision(); + virtual bool UpdateImpl(); + virtual bool WriteXMLUpdates(std::ostream& xml); + + /** Represent a subversion-reported action for one path in a revision. */ + struct Change + { + char Action; + std::string Path; + Change(): Action('?') {} + }; + + // Update status for files in each directory. + class Directory: public std::map<cmStdString, File> {}; + std::map<cmStdString, Directory> Dirs; // Old and new repository revisions. std::string OldRevision; @@ -52,11 +64,33 @@ private: // Directory under repository root checked out in working tree. std::string Base; + // Information known about old revision. + Revision PriorRev; + + // Information about revisions from a svn log. + std::list<Revision> Revisions; + std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + void GuessBase(std::vector<Change> const& changes); + const char* LocalPath(std::string const& path); + + void DoRevision(Revision const& revision, + std::vector<Change> const& changes); + void WriteXMLDirectory(std::ostream& xml, std::string const& path, + Directory const& dir); // Parsing helper classes. class InfoParser; + class LogParser; + class StatusParser; + class UpdateParser; friend class InfoParser; + friend class LogParser; + friend class StatusParser; + friend class UpdateParser; }; #endif diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx index b42ef5b..5ac5ebc 100644 --- a/Source/CTest/cmCTestUpdateHandler.cxx +++ b/Source/CTest/cmCTestUpdateHandler.cxx @@ -63,130 +63,6 @@ static const char* cmCTestUpdateHandlerUpdateToString(int type) return cmCTestUpdateHandlerUpdateStrings[type]; } -//---------------------------------------------------------------------- -//********************************************************************** -class cmCTestUpdateHandlerSVNXMLParser : public cmXMLParser -{ -public: - struct t_CommitLog - { - int Revision; - std::string Author; - std::string Date; - std::string Message; - }; - cmCTestUpdateHandlerSVNXMLParser(cmCTestUpdateHandler* up) - : cmXMLParser(), UpdateHandler(up), MinRevision(-1), MaxRevision(-1) - { - } - - int Parse(const char* str) - { - this->MinRevision = -1; - this->MaxRevision = -1; - int res = this->cmXMLParser::Parse(str); - if ( this->MinRevision == -1 || this->MaxRevision == -1 ) - { - return 0; - } - return res; - } - - typedef std::vector<t_CommitLog> t_VectorOfCommits; - - t_VectorOfCommits* GetCommits() { return &this->Commits; } - int GetMinRevision() { return this->MinRevision; } - int GetMaxRevision() { return this->MaxRevision; } - -protected: - void StartElement(const char* name, const char** atts) - { - if ( strcmp(name, "logentry") == 0 ) - { - this->CommitLog = t_CommitLog(); - const char* rev = this->FindAttribute(atts, "revision"); - if ( rev) - { - this->CommitLog.Revision = atoi(rev); - if ( this->MinRevision < 0 || - this->MinRevision > this->CommitLog.Revision ) - { - this->MinRevision = this->CommitLog.Revision; - } - if ( this->MaxRevision < 0 || - this->MaxRevision < this->CommitLog.Revision ) - { - this->MaxRevision = this->CommitLog.Revision; - } - } - } - this->CharacterData.erase( - this->CharacterData.begin(), this->CharacterData.end()); - } - void EndElement(const char* name) - { - if ( strcmp(name, "logentry") == 0 ) - { - cmCTestLog(this->UpdateHandler->GetCTestInstance(), - HANDLER_VERBOSE_OUTPUT, - "\tRevision: " << this->CommitLog.Revision<< std::endl - << "\tAuthor: " << this->CommitLog.Author.c_str() << std::endl - << "\tDate: " << this->CommitLog.Date.c_str() << std::endl - << "\tMessage: " << this->CommitLog.Message.c_str() << std::endl); - this->Commits.push_back(this->CommitLog); - } - else if ( strcmp(name, "author") == 0 ) - { - this->CommitLog.Author.assign(&(*(this->CharacterData.begin())), - this->CharacterData.size()); - } - else if ( strcmp(name, "date") == 0 ) - { - this->CommitLog.Date.assign(&(*(this->CharacterData.begin())), - this->CharacterData.size()); - } - else if ( strcmp(name, "msg") == 0 ) - { - this->CommitLog.Message.assign(&(*(this->CharacterData.begin())), - this->CharacterData.size()); - } - this->CharacterData.erase(this->CharacterData.begin(), - this->CharacterData.end()); - } - void CharacterDataHandler(const char* data, int length) - { - this->CharacterData.insert(this->CharacterData.end(), data, data+length); - } - const char* FindAttribute( const char** atts, const char* attribute ) - { - if ( !atts || !attribute ) - { - return 0; - } - const char **atr = atts; - while ( *atr && **atr && **(atr+1) ) - { - if ( strcmp(*atr, attribute) == 0 ) - { - return *(atr+1); - } - atr+=2; - } - return 0; - } - -private: - std::vector<char> CharacterData; - cmCTestUpdateHandler* UpdateHandler; - t_CommitLog CommitLog; - - t_VectorOfCommits Commits; - int MinRevision; - int MaxRevision; -}; -//********************************************************************** -//---------------------------------------------------------------------- - class cmCTestUpdateHandlerLocale { public: @@ -280,17 +156,10 @@ int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type) //functions and commented... int cmCTestUpdateHandler::ProcessHandler() { - int count = 0; - std::string::size_type cc, kk; - std::string goutput; - std::string errors; - // Make sure VCS tool messages are in English so we can parse them. cmCTestUpdateHandlerLocale fixLocale; static_cast<void>(fixLocale); - int retVal = 0; - // Get source dir const char* sourceDirectory = this->GetOption("SourceDirectory"); if ( !sourceDirectory ) @@ -340,64 +209,9 @@ int cmCTestUpdateHandler::ProcessHandler() vc->SetCommandLineTool(this->UpdateCommand); vc->SetSourceDirectory(sourceDirectory); - // And update options - std::string updateOptions - = this->CTest->GetCTestConfiguration("UpdateOptions"); - if ( updateOptions.empty() ) - { - switch (this->UpdateType) - { - case cmCTestUpdateHandler::e_CVS: - updateOptions = this->CTest->GetCTestConfiguration("CVSUpdateOptions"); - if ( updateOptions.empty() ) - { - updateOptions = "-dP"; - } - break; - case cmCTestUpdateHandler::e_SVN: - updateOptions = this->CTest->GetCTestConfiguration("SVNUpdateOptions"); - break; - } - } - - // Get update time - std::string extra_update_opts; - if ( this->CTest->GetTestModel() == cmCTest::NIGHTLY ) - { - std::string today_update_date = vc->GetNightlyTime(); - - // TODO: SVN - switch ( this->UpdateType ) - { - case cmCTestUpdateHandler::e_CVS: - extra_update_opts += "-D \"" + today_update_date +" UTC\""; - break; - case cmCTestUpdateHandler::e_SVN: - extra_update_opts += "-r \"{" + today_update_date +" +0000}\""; - break; - } - } - // Cleanup the working tree. vc->Cleanup(); - bool res = true; - - // CVS variables - // SVN variables - int svn_current_revision = 0; - int svn_latest_revision = 0; - int svn_use_status = 0; - - // Get initial repository information if that is possible. - vc->MarkOldRevision(); - if(this->UpdateType == e_SVN) - { - svn_current_revision = - static_cast<cmCTestSVN*>(vc.get())->GetOldRevision(); - } - - // // Now update repository and remember what files were updated // @@ -413,56 +227,7 @@ int cmCTestUpdateHandler::ProcessHandler() static_cast<unsigned int>(cmSystemTools::GetTime()); double elapsed_time_start = cmSystemTools::GetTime(); - std::string command; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "* Update repository: " - << command.c_str() << std::endl); - if ( !this->CTest->GetShowOnly() ) - { - command = ""; - switch( this->UpdateType ) - { - case cmCTestUpdateHandler::e_CVS: - command = "\""+this->UpdateCommand+"\" -z3 update " + updateOptions + - " " + extra_update_opts; - ofs << "* Update repository: " << std::endl; - ofs << " Command: " << command.c_str() << std::endl; - res = this->CTest->RunCommand(command.c_str(), &goutput, &errors, - &retVal, sourceDirectory, 0 /*this->TimeOut*/); - ofs << " Output: " << goutput.c_str() << std::endl; - ofs << " Errors: " << errors.c_str() << std::endl; - break; - case cmCTestUpdateHandler::e_SVN: - { - std::string partialOutput; - command = "\"" + this->UpdateCommand + "\" update " + updateOptions + - " " + extra_update_opts; - ofs << "* Update repository: " << std::endl; - ofs << " Command: " << command.c_str() << std::endl; - bool res1 = this->CTest->RunCommand(command.c_str(), &partialOutput, - &errors, - &retVal, sourceDirectory, 0 /*this->TimeOut*/); - ofs << " Output: " << partialOutput.c_str() << std::endl; - ofs << " Errors: " << errors.c_str() << std::endl; - goutput = partialOutput; - command = "\"" + this->UpdateCommand + "\" status"; - ofs << "* Status repository: " << std::endl; - ofs << " Command: " << command.c_str() << std::endl; - res = this->CTest->RunCommand(command.c_str(), &partialOutput, - &errors, &retVal, sourceDirectory, 0 /*this->TimeOut*/); - ofs << " Output: " << partialOutput.c_str() << std::endl; - ofs << " Errors: " << errors.c_str() << std::endl; - goutput += partialOutput; - res = res && res1; - ofs << " Total output of update: " << goutput.c_str() << std::endl; - } - } - if ( ofs ) - { - ofs << "--- Update repository ---" << std::endl; - ofs << goutput << std::endl; - } - } - bool updateProducedError = !res || retVal; + bool updated = vc->Update(); os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" << "<Update mode=\"Client\" Generator=\"ctest-" @@ -474,404 +239,31 @@ int cmCTestUpdateHandler::ProcessHandler() << this->CTest->GetTestModelString() << "</BuildStamp>" << std::endl; os << "\t<StartDateTime>" << start_time << "</StartDateTime>\n" << "\t<StartTime>" << start_time_time << "</StartTime>\n" - << "\t<UpdateCommand>" << cmXMLSafe(command) + << "\t<UpdateCommand>" << cmXMLSafe(vc->GetUpdateCommandLine()) << "</UpdateCommand>\n" << "\t<UpdateType>" << cmXMLSafe( cmCTestUpdateHandlerUpdateToString(this->UpdateType)) << "</UpdateType>\n"; - // Even though it failed, we may have some useful information. Try to - // continue... - std::vector<cmStdString> lines; - cmSystemTools::Split(goutput.c_str(), lines); - std::vector<cmStdString> errLines; - cmSystemTools::Split(errors.c_str(), errLines); - lines.insert(lines.end(), errLines.begin(), errLines.end()); - - // CVS style regular expressions - cmsys::RegularExpression cvs_date_author_regex( - "^date: +([^;]+); +author: +([^;]+); +state: +[^;]+;"); - cmsys::RegularExpression cvs_revision_regex("^revision +([^ ]*) *$"); - cmsys::RegularExpression cvs_end_of_file_regex( - "^==========================================" - "===================================$"); - cmsys::RegularExpression cvs_end_of_comment_regex( - "^----------------------------$"); - - // Subversion style regular expressions - cmsys::RegularExpression svn_status_line_regex( - "^ *([0-9]+) *([0-9]+) *([^ ]+) *([^ ][^\t\r\n]*)[ \t\r\n]*$"); - cmsys::RegularExpression svn_latest_revision_regex( - "(Updated to|At) revision ([0-9]+)\\."); - - cmsys::RegularExpression file_removed_line( - "cvs update: `?([^']*)'? is no longer in the repository"); - cmsys::RegularExpression file_removed_line2( - "cvs update: warning: `?([^']*)'? is not \\(any longer\\) pertinent"); - cmsys::RegularExpression file_update_line("([A-Z]) *(.*)"); - std::string current_path = "<no-path>"; - bool first_file = true; - - std::set<cmStdString> author_set; - int numUpdated = 0; - int numModified = 0; - int numConflicting = 0; - - // Get final repository information if that is possible. - vc->MarkNewRevision(); - if ( this->UpdateType == cmCTestUpdateHandler::e_SVN ) - { - svn_latest_revision = - static_cast<cmCTestSVN*>(vc.get())->GetNewRevision(); - } - - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Gathering version information (each . represents one updated file):" - << std::endl); - int file_count = 0; - std::string removed_line; - for ( cc= 0; cc < lines.size(); cc ++ ) - { - const char* line = lines[cc].c_str(); - if ( file_removed_line.find(line) ) - { - removed_line = "D " + file_removed_line.match(1); - line = removed_line.c_str(); - } - else if ( file_removed_line2.find(line) ) - { - removed_line = "D " + file_removed_line2.match(1); - line = removed_line.c_str(); - } - if ( file_update_line.find(line) ) - { - if ( file_count == 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << std::flush); - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); - std::string upChar = file_update_line.match(1); - std::string upFile = file_update_line.match(2); - char mod = upChar[0]; - bool notLocallyModified = false; - if ( mod == 'X' || mod == 'L') - { - continue; - } - if ( mod != 'M' && mod != 'C' && mod != 'G' ) - { - count ++; - notLocallyModified = true; - } - const char* file = upFile.c_str(); - cmCTestLog(this->CTest, DEBUG, "Line" << cc << ": " << mod << " - " - << file << std::endl); + vc->WriteXML(os); - std::string output; - if ( notLocallyModified ) - { - std::string logcommand; - switch ( this->UpdateType ) - { - case cmCTestUpdateHandler::e_CVS: - logcommand = "\"" + this->UpdateCommand + "\" -z3 log -N \"" - + file + "\""; - break; - case cmCTestUpdateHandler::e_SVN: - if ( svn_latest_revision > 0 && - svn_latest_revision > svn_current_revision ) - { - cmOStringStream logCommandStream; - logCommandStream << "\"" << this->UpdateCommand << "\" log -r " - << svn_current_revision << ":" << svn_latest_revision - << " --xml \"" << file << "\""; - logcommand = logCommandStream.str(); - } - else - { - logcommand = "\"" + this->UpdateCommand + - "\" status --verbose \"" + file + "\""; - svn_use_status = 1; - } - break; - } - cmCTestLog(this->CTest, DEBUG, "Do log: " << logcommand << std::endl); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "* Get file update information: " << logcommand.c_str() - << std::endl); - ofs << "* Get log information for file: " << file << std::endl; - ofs << " Command: " << logcommand.c_str() << std::endl; - res = this->CTest->RunCommand(logcommand.c_str(), &output, &errors, - &retVal, sourceDirectory, 0 /*this->TimeOut*/); - ofs << " Output: " << output.c_str() << std::endl; - ofs << " Errors: " << errors.c_str() << std::endl; - if ( ofs ) - { - ofs << output << std::endl; - } - } - else - { - res = false; - } - if ( res ) - { - cmCTestLog(this->CTest, DEBUG, output << std::endl); - std::string::size_type sline = 0; - std::string srevision1 = "Unknown"; - std::string sdate1 = "Unknown"; - std::string sauthor1 = "Unknown"; - std::string semail1 = "Unknown"; - std::string comment1 = ""; - std::string srevision2 = "Unknown"; - if ( this->UpdateType == cmCTestUpdateHandler::e_CVS ) - { - bool have_first = false; - bool have_second = false; - std::vector<cmStdString> ulines; - cmSystemTools::Split(output.c_str(), ulines); - for ( kk = 0; kk < ulines.size(); kk ++ ) - { - const char* clp = ulines[kk].c_str(); - if ( !have_second && !sline && cvs_revision_regex.find(clp) ) - { - if ( !have_first ) - { - srevision1 = cvs_revision_regex.match(1); - } - else - { - srevision2 = cvs_revision_regex.match(1); - } - } - else if ( !have_second && !sline && - cvs_date_author_regex.find(clp) ) - { - sline = kk + 1; - if ( !have_first ) - { - sdate1 = cvs_date_author_regex.match(1); - sauthor1 = cvs_date_author_regex.match(2); - } - } - else if ( sline && cvs_end_of_comment_regex.find(clp) || - cvs_end_of_file_regex.find(clp)) - { - if ( !have_first ) - { - have_first = true; - } - else if ( !have_second ) - { - have_second = true; - } - sline = 0; - } - else if ( sline ) - { - if ( !have_first ) - { - comment1 += clp; - comment1 += "\n"; - } - } - } - } - else if ( this->UpdateType == cmCTestUpdateHandler::e_SVN ) - { - if ( svn_use_status ) - { - cmOStringStream str; - str << svn_current_revision; - srevision1 = str.str(); - if (!svn_status_line_regex.find(output)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Bad output from SVN status command: " << output - << std::endl); - } - else if ( svn_status_line_regex.match(4) != file ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Bad output from SVN status command. " - "The file name returned: \"" - << svn_status_line_regex.match(4) - << "\" was different than the file specified: \"" << file - << "\"" << std::endl); - } - else - { - srevision1 = svn_status_line_regex.match(2); - int latest_revision = atoi( - svn_status_line_regex.match(2).c_str()); - if ( svn_current_revision < latest_revision ) - { - srevision2 = str.str(); - } - sauthor1 = svn_status_line_regex.match(3); - } - } - else - { - cmCTestUpdateHandlerSVNXMLParser parser(this); - if ( parser.Parse(output.c_str()) ) - { - int minrev = parser.GetMinRevision(); - int maxrev = parser.GetMaxRevision(); - cmCTestUpdateHandlerSVNXMLParser:: - t_VectorOfCommits::iterator it; - for ( it = parser.GetCommits()->begin(); - it != parser.GetCommits()->end(); - ++ it ) - { - if ( it->Revision == maxrev ) - { - cmOStringStream mRevStream; - mRevStream << maxrev; - srevision1 = mRevStream.str(); - sauthor1 = it->Author; - comment1 = it->Message; - sdate1 = it->Date; - } - else if ( it->Revision == minrev ) - { - cmOStringStream mRevStream; - mRevStream << minrev; - srevision2 = mRevStream.str(); - } - } - } - } - } - if ( mod == 'M' ) - { - comment1 = "Locally modified file\n"; - sauthor1 = "Local User"; - } - if ( mod == 'D' ) - { - comment1 += " - Removed file\n"; - } - if ( mod == 'C' ) - { - comment1 = "Conflict while updating\n"; - sauthor1 = "Local User"; - } - std::string path = cmSystemTools::GetFilenamePath(file); - std::string fname = cmSystemTools::GetFilenameName(file); - if ( path != current_path ) - { - if ( !first_file ) - { - os << "\t</Directory>" << std::endl; - } - else - { - first_file = false; - } - os << "\t<Directory>\n" - << "\t\t<Name>" << path << "</Name>" << std::endl; - } - if ( mod == 'C' ) - { - numConflicting ++; - os << "\t<Conflicting>" << std::endl; - } - else if ( mod == 'G' ) - { - numConflicting ++; - os << "\t<Conflicting>" << std::endl; - } - else if ( mod == 'M' ) - { - numModified ++; - os << "\t<Modified>" << std::endl; - } - else - { - numUpdated ++; - os << "\t<Updated>" << std::endl; - } - if ( srevision2 == "Unknown" ) - { - srevision2 = srevision1; - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "File: " - << path.c_str() << " / " << fname.c_str() << " was updated by " - << sauthor1.c_str() << " to revision: " << srevision1.c_str() - << " from revision: " << srevision2.c_str() << std::endl); - os << "\t\t<File>" - << cmXMLSafe(fname) - << "</File>\n" - << "\t\t<Directory>" << cmXMLSafe(path) - << "</Directory>\n" - << "\t\t<FullName>" << cmXMLSafe(file) << "</FullName>\n" - << "\t\t<CheckinDate>" << cmXMLSafe(sdate1) - << "</CheckinDate>\n" - << "\t\t<Author>" << cmXMLSafe(sauthor1) << "</Author>\n" - << "\t\t<Email>" << cmXMLSafe(semail1) << "</Email>\n" - << "\t\t<Log>" << cmXMLSafe(comment1) << "</Log>\n" - << "\t\t<Revision>" << srevision1 << "</Revision>\n" - << "\t\t<PriorRevision>" << srevision2 << "</PriorRevision>" - << std::endl; - if ( mod == 'C' ) - { - os << "\t</Conflicting>" << std::endl; - } - else if ( mod == 'G' ) - { - os << "\t</Conflicting>" << std::endl; - } - else if ( mod == 'M' ) - { - os << "\t</Modified>" << std::endl; - } - else - { - os << "\t</Updated>" << std::endl; - } - author_set.insert(sauthor1); - current_path = path; - } - file_count ++; - } - } - if ( file_count ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - } - if ( numUpdated ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numUpdated - << " updated files" << std::endl); - } - if ( numModified ) + int localModifications = 0; + if(int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated)) { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numModified - << " locally modified files" - << std::endl); - } - if ( numConflicting ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numConflicting - << " conflicting files" - << std::endl); - } - if ( numModified == 0 && numConflicting == 0 && numUpdated == 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Project is up-to-date" - << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Found " << numUpdated << " updated files\n"); } - if ( !first_file ) + if(int numModified = vc->GetPathCount(cmCTestVC::PathModified)) { - os << "\t</Directory>" << std::endl; + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Found " << numModified << " locally modified files\n"); + localModifications += numModified; } - - // TODO: Skip the author list when submitting to CDash. - for(std::set<cmStdString>::const_iterator ai = author_set.begin(); - ai != author_set.end(); ++ai) + if(int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting)) { - os << "\t<Author><Name>" << cmXMLSafe(*ai) << "</Name></Author>\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Found " << numConflicting << " conflicting files\n"); + localModifications += numConflicting; } cmCTestLog(this->CTest, DEBUG, "End" << std::endl); @@ -883,7 +275,7 @@ int cmCTestUpdateHandler::ProcessHandler() static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 << "</ElapsedMinutes>\n" << "\t<UpdateReturnStatus>"; - if ( numModified > 0 || numConflicting > 0 ) + if(localModifications) { os << "Update error: There are modified or conflicting files in the " "repository"; @@ -891,23 +283,14 @@ int cmCTestUpdateHandler::ProcessHandler() " There are modified or conflicting files in the repository" << std::endl); } - if ( updateProducedError ) + if(!updated) { - os << "Update error: "; - cmCTestLog(this->CTest, ERROR_MESSAGE, " Update with command: " - << command << " failed" << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, " Update command failed: " + << vc->GetUpdateCommandLine() << "\n"); } os << "</UpdateReturnStatus>" << std::endl; os << "</Update>" << std::endl; - if (! res ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Error(s) when updating the project" << std::endl); - cmCTestLog(this->CTest, ERROR_MESSAGE, "Output: " - << goutput << std::endl); - return -1; - } - return count; + return localModifications; } //---------------------------------------------------------------------- diff --git a/Source/CTest/cmCTestVC.cxx b/Source/CTest/cmCTestVC.cxx index ccc6fcb..642a59a 100644 --- a/Source/CTest/cmCTestVC.cxx +++ b/Source/CTest/cmCTestVC.cxx @@ -17,12 +17,19 @@ #include "cmCTestVC.h" #include "cmCTest.h" +#include "cmXMLSafe.h" #include <cmsys/Process.h> //---------------------------------------------------------------------------- cmCTestVC::cmCTestVC(cmCTest* ct, std::ostream& log): CTest(ct), Log(log) { + this->PathCount[PathUpdated] = 0; + this->PathCount[PathModified] = 0; + this->PathCount[PathConflicting] = 0; + this->Unknown.Date = "Unknown"; + this->Unknown.Author = "Unknown"; + this->Unknown.Rev = "Unknown"; } //---------------------------------------------------------------------------- @@ -72,6 +79,22 @@ std::string cmCTestVC::ComputeCommandLine(char const* const* cmd) } //---------------------------------------------------------------------------- +bool cmCTestVC::RunUpdateCommand(char const* const* cmd, + OutputParser* out, OutputParser* err) +{ + // Report the command line. + this->UpdateCommandLine = this->ComputeCommandLine(cmd); + if(this->CTest->GetShowOnly()) + { + this->Log << this->UpdateCommandLine << "\n"; + return true; + } + + // Run the command. + return this->RunChild(cmd, out, err); +} + +//---------------------------------------------------------------------------- std::string cmCTestVC::GetNightlyTime() { // Get the nightly start time corresponding to the current dau. @@ -104,6 +127,17 @@ void cmCTestVC::CleanupImpl() } //---------------------------------------------------------------------------- +bool cmCTestVC::Update() +{ + this->NoteOldRevision(); + this->Log << "--- Begin Update ---\n"; + bool result = this->UpdateImpl(); + this->Log << "--- End Update ---\n"; + this->NoteNewRevision(); + return result; +} + +//---------------------------------------------------------------------------- void cmCTestVC::NoteOldRevision() { // We do nothing by default. @@ -114,3 +148,51 @@ void cmCTestVC::NoteNewRevision() { // We do nothing by default. } + +//---------------------------------------------------------------------------- +bool cmCTestVC::UpdateImpl() +{ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "* Unknown VCS tool, not updating!" << std::endl); + return true; +} + +//---------------------------------------------------------------------------- +bool cmCTestVC::WriteXML(std::ostream& xml) +{ + this->Log << "--- Begin Revisions ---\n"; + bool result = this->WriteXMLUpdates(xml); + this->Log << "--- End Revisions ---\n"; + return result; +} + +//---------------------------------------------------------------------------- +bool cmCTestVC::WriteXMLUpdates(std::ostream&) +{ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "* CTest cannot extract updates for this VCS tool.\n"); + return true; +} + +//---------------------------------------------------------------------------- +void cmCTestVC::WriteXMLEntry(std::ostream& xml, + std::string const& path, + std::string const& name, + std::string const& full, + File const& f) +{ + static const char* desc[3] = { "Updated", "Modified", "Conflicting"}; + Revision const& rev = f.Rev? *f.Rev : this->Unknown; + std::string prior = f.PriorRev? f.PriorRev->Rev : std::string("Unknown"); + xml << "\t\t<" << desc[f.Status] << ">\n" + << "\t\t\t<File>" << cmXMLSafe(name) << "</File>\n" + << "\t\t\t<Directory>" << cmXMLSafe(path) << "</Directory>\n" + << "\t\t\t<FullName>" << cmXMLSafe(full) << "</FullName>\n" + << "\t\t\t<CheckinDate>" << cmXMLSafe(rev.Date) << "</CheckinDate>\n" + << "\t\t\t<Author>" << cmXMLSafe(rev.Author) << "</Author>\n" + << "\t\t\t<Log>" << cmXMLSafe(rev.Log) << "</Log>\n" + << "\t\t\t<Revision>" << cmXMLSafe(rev.Rev) << "</Revision>\n" + << "\t\t\t<PriorRevision>" << cmXMLSafe(prior) << "</PriorRevision>\n" + << "\t\t</" << desc[f.Status] << ">\n"; + ++this->PathCount[f.Status]; +} diff --git a/Source/CTest/cmCTestVC.h b/Source/CTest/cmCTestVC.h index 4413193..3e24e2f 100644 --- a/Source/CTest/cmCTestVC.h +++ b/Source/CTest/cmCTestVC.h @@ -45,13 +45,49 @@ public: /** Perform cleanup operations on the work tree. */ void Cleanup(); - void MarkOldRevision() { this->NoteOldRevision(); } - void MarkNewRevision() { this->NoteNewRevision(); } + /** Update the working tree to the new revision. */ + bool Update(); + + /** Get the command line used by the Update method. */ + std::string const& GetUpdateCommandLine() const + { return this->UpdateCommandLine; } + + /** Write Update.xml entries for the updates found. */ + bool WriteXML(std::ostream& xml); + + /** Enumerate non-trivial working tree states during update. */ + enum PathStatus { PathUpdated, PathModified, PathConflicting }; + + /** Get the number of working tree paths in each state after update. */ + int GetPathCount(PathStatus s) const { return this->PathCount[s]; } + protected: // Internal API to be implemented by subclasses. virtual void CleanupImpl(); virtual void NoteOldRevision(); + virtual bool UpdateImpl(); virtual void NoteNewRevision(); + virtual bool WriteXMLUpdates(std::ostream& xml); + + /** Basic information about one revision of a tree or file. */ + struct Revision + { + std::string Rev; + std::string Date; + std::string Author; + std::string Log; + }; + + /** Represent change to one file. */ + struct File + { + PathStatus Status; + Revision const* Rev; + Revision const* PriorRev; + File(): Status(PathUpdated), Rev(0), PriorRev(0) {} + File(PathStatus status, Revision const* rev, Revision const* priorRev): + Status(status), Rev(rev), PriorRev(priorRev) {} + }; /** Convert a list of arguments to a human-readable command line. */ static std::string ComputeCommandLine(char const* const* cmd); @@ -60,6 +96,15 @@ protected: bool RunChild(char const* const* cmd, OutputParser* out, OutputParser* err, const char* workDir = 0); + /** Run VC update command line and send output to given parsers. */ + bool RunUpdateCommand(char const* const* cmd, + OutputParser* out, OutputParser* err = 0); + + /** Write xml element for one file. */ + void WriteXMLEntry(std::ostream& xml, std::string const& path, + std::string const& name, std::string const& full, + File const& f); + // Instance of cmCTest running the script. cmCTest* CTest; @@ -69,6 +114,15 @@ protected: // Basic information about the working tree. std::string CommandLineTool; std::string SourceDirectory; + + // Record update command info. + std::string UpdateCommandLine; + + // Placeholder for unknown revisions. + Revision Unknown; + + // Count paths reported with each PathStatus value. + int PathCount[3]; }; #endif |