[Cmake-commits] [cmake-commits] king committed cmCTestCVS.cxx 1.1 1.2 cmCTestCVS.h 1.1 1.2 cmCTestSVN.cxx 1.4 1.5 cmCTestSVN.h 1.4 1.5 cmCTestUpdateHandler.cxx 1.61 1.62 cmCTestVC.cxx 1.6 1.7 cmCTestVC.h 1.5 1.6

cmake-commits at cmake.org cmake-commits at cmake.org
Wed Feb 25 14:42:47 EST 2009


Update of /cvsroot/CMake/CMake/Source/CTest
In directory public:/mounts/ram/cvs-serv30309/Source/CTest

Modified Files:
	cmCTestCVS.cxx cmCTestCVS.h cmCTestSVN.cxx cmCTestSVN.h 
	cmCTestUpdateHandler.cxx cmCTestVC.cxx cmCTestVC.h 
Log Message:
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.


Index: cmCTestSVN.h
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestSVN.h,v
retrieving revision 1.4
retrieving revision 1.5
diff -C 2 -d -r1.4 -r1.5
*** cmCTestSVN.h	24 Feb 2009 20:43:25 -0000	1.4
--- cmCTestSVN.h	25 Feb 2009 19:42:45 -0000	1.5
***************
*** 32,37 ****
    virtual ~cmCTestSVN();
  
-   int GetOldRevision() { return atoi(this->OldRevision.c_str()); }
-   int GetNewRevision() { return atoi(this->NewRevision.c_str()); }
  private:
    // Implement cmCTestVC internal API.
--- 32,35 ----
***************
*** 39,42 ****
--- 37,54 ----
    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.
***************
*** 53,61 ****
--- 65,95 ----
    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;
  };
  

Index: cmCTestSVN.cxx
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestSVN.cxx,v
retrieving revision 1.4
retrieving revision 1.5
diff -C 2 -d -r1.4 -r1.5
*** cmCTestSVN.cxx	24 Feb 2009 20:43:25 -0000	1.4
--- cmCTestSVN.cxx	25 Feb 2009 19:42:45 -0000	1.5
***************
*** 18,21 ****
--- 18,24 ----
  
  #include "cmCTest.h"
+ #include "cmSystemTools.h"
+ #include "cmXMLParser.h"
+ #include "cmXMLSafe.h"
  
  #include <cmsys/RegularExpression.hxx>
***************
*** 24,27 ****
--- 27,31 ----
  cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log)
  {
+   this->PriorRev = this->Unknown;
  }
  
***************
*** 115,118 ****
--- 119,123 ----
    cmCTestLog(this->CTest, HANDLER_OUTPUT, "   Old revision of repository is: "
               << this->OldRevision << "\n");
+   this->PriorRev.Rev = this->OldRevision;
  }
  
***************
*** 125,128 ****
--- 130,134 ----
               << this->NewRevision << "\n");
  
+   // this->Root = ""; // uncomment to test GuessBase
    this->Log << "URL = " << this->URL << "\n";
    this->Log << "Root = " << this->Root << "\n";
***************
*** 137,138 ****
--- 143,528 ----
    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;
+ }

Index: cmCTestCVS.cxx
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestCVS.cxx,v
retrieving revision 1.1
retrieving revision 1.2
diff -C 2 -d -r1.1 -r1.2
*** cmCTestCVS.cxx	24 Feb 2009 15:39:54 -0000	1.1
--- cmCTestCVS.cxx	25 Feb 2009 19:42:45 -0000	1.2
***************
*** 17,20 ****
--- 17,26 ----
  #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)
***************
*** 26,27 ****
--- 32,323 ----
  {
  }
+ 
+ //----------------------------------------------------------------------------
+ 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;
+ }

Index: cmCTestVC.cxx
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestVC.cxx,v
retrieving revision 1.6
retrieving revision 1.7
diff -C 2 -d -r1.6 -r1.7
*** cmCTestVC.cxx	25 Feb 2009 14:20:26 -0000	1.6
--- cmCTestVC.cxx	25 Feb 2009 19:42:45 -0000	1.7
***************
*** 18,21 ****
--- 18,22 ----
  
  #include "cmCTest.h"
+ #include "cmXMLSafe.h"
  
  #include <cmsys/Process.h>
***************
*** 24,27 ****
--- 25,34 ----
  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";
  }
  
***************
*** 73,76 ****
--- 80,99 ----
  
  //----------------------------------------------------------------------------
+ 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()
  {
***************
*** 105,108 ****
--- 128,142 ----
  
  //----------------------------------------------------------------------------
+ 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()
  {
***************
*** 115,116 ****
--- 149,198 ----
    // 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];
+ }

Index: cmCTestUpdateHandler.cxx
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v
retrieving revision 1.61
retrieving revision 1.62
diff -C 2 -d -r1.61 -r1.62
*** cmCTestUpdateHandler.cxx	24 Feb 2009 17:52:36 -0000	1.61
--- cmCTestUpdateHandler.cxx	25 Feb 2009 19:42:45 -0000	1.62
***************
*** 64,191 ****
  }
  
- //----------------------------------------------------------------------
- //**********************************************************************
- 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
  {
--- 64,67 ----
***************
*** 281,295 ****
  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");
--- 157,164 ----
***************
*** 341,402 ****
    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
--- 210,216 ----
***************
*** 414,467 ****
    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;
  
    os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
--- 228,232 ----
    double elapsed_time_start = cmSystemTools::GetTime();
  
!   bool updated = vc->Update();
  
    os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
***************
*** 475,479 ****
    os << "\t<StartDateTime>" << start_time << "</StartDateTime>\n"
      << "\t<StartTime>" << start_time_time << "</StartTime>\n"
!     << "\t<UpdateCommand>" << cmXMLSafe(command)
      << "</UpdateCommand>\n"
      << "\t<UpdateType>" << cmXMLSafe(
--- 240,244 ----
    os << "\t<StartDateTime>" << start_time << "</StartDateTime>\n"
      << "\t<StartTime>" << start_time_time << "</StartTime>\n"
!     << "\t<UpdateCommand>" << cmXMLSafe(vc->GetUpdateCommandLine())
      << "</UpdateCommand>\n"
      << "\t<UpdateType>" << cmXMLSafe(
***************
*** 481,876 ****
      << "</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);
  
!       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 )
!     {
!     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";
      }
  
--- 246,268 ----
      << "</UpdateType>\n";
  
!   vc->WriteXML(os);
  
!   int localModifications = 0;
!   if(int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated))
      {
!     cmCTestLog(this->CTest, HANDLER_OUTPUT,
!                "   Found " << numUpdated << " updated files\n");
      }
!   if(int numModified = vc->GetPathCount(cmCTestVC::PathModified))
      {
!     cmCTestLog(this->CTest, HANDLER_OUTPUT,
!                "   Found " << numModified << " locally modified files\n");
!     localModifications += numModified;
      }
!   if(int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting))
      {
!     cmCTestLog(this->CTest, HANDLER_OUTPUT,
!                "   Found " << numConflicting << " conflicting files\n");
!     localModifications += numConflicting;
      }
  
***************
*** 884,888 ****
      << "</ElapsedMinutes>\n"
      << "\t<UpdateReturnStatus>";
!   if ( numModified > 0 || numConflicting > 0 )
      {
      os << "Update error: There are modified or conflicting files in the "
--- 276,280 ----
      << "</ElapsedMinutes>\n"
      << "\t<UpdateReturnStatus>";
!   if(localModifications)
      {
      os << "Update error: There are modified or conflicting files in the "
***************
*** 892,912 ****
        << std::endl);
      }
!   if ( updateProducedError )
      {
!     os << "Update error: ";
!     cmCTestLog(this->CTest, ERROR_MESSAGE, "   Update with command: "
!       << command << " failed" << std::endl);
      }
    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;
  }
  
--- 284,295 ----
        << std::endl);
      }
!   if(!updated)
      {
!     cmCTestLog(this->CTest, ERROR_MESSAGE, "   Update command failed: "
!                << vc->GetUpdateCommandLine() << "\n");
      }
    os << "</UpdateReturnStatus>" << std::endl;
    os << "</Update>" << std::endl;
!   return localModifications;
  }
  

Index: cmCTestVC.h
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestVC.h,v
retrieving revision 1.5
retrieving revision 1.6
diff -C 2 -d -r1.5 -r1.6
*** cmCTestVC.h	24 Feb 2009 17:52:36 -0000	1.5
--- cmCTestVC.h	25 Feb 2009 19:42:45 -0000	1.6
***************
*** 46,56 ****
    void Cleanup();
  
!   void MarkOldRevision() { this->NoteOldRevision(); }
!   void MarkNewRevision() { this->NoteNewRevision(); }
  protected:
    // Internal API to be implemented by subclasses.
    virtual void CleanupImpl();
    virtual void NoteOldRevision();
    virtual void NoteNewRevision();
  
    /** Convert a list of arguments to a human-readable command line.  */
--- 46,92 ----
    void Cleanup();
  
!   /** 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.  */
***************
*** 61,64 ****
--- 97,109 ----
                  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;
***************
*** 70,73 ****
--- 115,127 ----
    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];
  };
  

Index: cmCTestCVS.h
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestCVS.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -C 2 -d -r1.1 -r1.2
*** cmCTestCVS.h	24 Feb 2009 15:39:55 -0000	1.1
--- cmCTestCVS.h	25 Feb 2009 19:42:45 -0000	1.2
***************
*** 31,34 ****
--- 31,55 ----
  
    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;
  };
  



More information about the Cmake-commits mailing list