summaryrefslogtreecommitdiffstats
path: root/Source/CTest
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2009-02-25 19:42:45 (GMT)
committerBrad King <brad.king@kitware.com>2009-02-25 19:42:45 (GMT)
commit80282b749fb138ea8bd188dd5b7623c7545ea927 (patch)
treea654e1b28bf2b2bec97de9ee7dad695d322598f1 /Source/CTest
parentcb788e8f6dfeeb5a934679f671adc87116837834 (diff)
downloadCMake-80282b749fb138ea8bd188dd5b7623c7545ea927.zip
CMake-80282b749fb138ea8bd188dd5b7623c7545ea927.tar.gz
CMake-80282b749fb138ea8bd188dd5b7623c7545ea927.tar.bz2
ENH: Rewrite CTest Update implementation
This adds a new VCS update implementation to the cmCTestVC hierarchy and removes it from cmCTestUpdateHandler. The new implementation has the following advantages: - Factorized implementation instead of monolithic function - Logs vcs tool output as it is parsed (less memory, inline messages) - Uses one global svn log instead of one log per file - Reports changes on cvs branches (instead of latest trunk change) - Generates simpler Update.xml (only one Directory element per dir) Shared components of the new implementation appear in cmCTestVC and may be re-used by subclasses for other VCS tools in the future.
Diffstat (limited to 'Source/CTest')
-rw-r--r--Source/CTest/cmCTestCVS.cxx296
-rw-r--r--Source/CTest/cmCTestCVS.h21
-rw-r--r--Source/CTest/cmCTestSVN.cxx390
-rw-r--r--Source/CTest/cmCTestSVN.h38
-rw-r--r--Source/CTest/cmCTestUpdateHandler.cxx657
-rw-r--r--Source/CTest/cmCTestVC.cxx82
-rw-r--r--Source/CTest/cmCTestVC.h58
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