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.
This commit is contained in:
Brad King 2009-02-25 14:42:45 -05:00
parent cb788e8f6d
commit 80282b749f
7 changed files with 901 additions and 641 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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());
vc->WriteXML(os);
// 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 )
int localModifications = 0;
if(int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated))
{
svn_latest_revision =
static_cast<cmCTestSVN*>(vc.get())->GetNewRevision();
cmCTestLog(this->CTest, HANDLER_OUTPUT,
" Found " << numUpdated << " updated files\n");
}
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 ++ )
if(int numModified = vc->GetPathCount(cmCTestVC::PathModified))
{
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);
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 ++;
}
cmCTestLog(this->CTest, HANDLER_OUTPUT,
" Found " << numModified << " locally modified files\n");
localModifications += numModified;
}
if ( file_count )
if(int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting))
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);
}
if ( numUpdated )
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numUpdated
<< " updated files" << std::endl);
}
if ( numModified )
{
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);
}
if ( !first_file )
{
os << "\t</Directory>" << std::endl;
}
// TODO: Skip the author list when submitting to CDash.
for(std::set<cmStdString>::const_iterator ai = author_set.begin();
ai != author_set.end(); ++ai)
{
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;
}
//----------------------------------------------------------------------

View File

@ -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";
}
//----------------------------------------------------------------------------
@ -71,6 +78,22 @@ std::string cmCTestVC::ComputeCommandLine(char const* const* cmd)
return line.str();
}
//----------------------------------------------------------------------------
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()
{
@ -103,6 +126,17 @@ void cmCTestVC::CleanupImpl()
// We do no cleanup by default.
}
//----------------------------------------------------------------------------
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()
{
@ -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];
}

View File

@ -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