[Cmake-commits] [cmake-commits] king committed cmCTestHG.cxx NONE 1.1 cmCTestHG.h NONE 1.1 cmCTestUpdateCommand.cxx 1.17 1.18 cmCTestUpdateHandler.cxx 1.66 1.67 cmCTestUpdateHandler.h 1.13 1.14

cmake-commits at cmake.org cmake-commits at cmake.org
Fri Jul 10 11:08:03 EDT 2009


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

Modified Files:
	cmCTestUpdateCommand.cxx cmCTestUpdateHandler.cxx 
	cmCTestUpdateHandler.h 
Added Files:
	cmCTestHG.cxx cmCTestHG.h 
Log Message:
ENH: Teach CTest to handle Mercurial repositories

This creates cmCTestHG to drive CTest Update handling on hg-based work
trees.  Currently we always update to the head of the remote tracking
branch (hg pull), so the nightly start time is ignored for Nightly
builds.  A later change will address this.

See issue #7879.  Patch from Emmanuel Christophe.  I modified the patch
slightly for code style, to finish up some parsing details, and to fix
the test.


Index: cmCTestUpdateHandler.cxx
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v
retrieving revision 1.66
retrieving revision 1.67
diff -C 2 -d -r1.66 -r1.67
*** cmCTestUpdateHandler.cxx	14 May 2009 20:13:51 -0000	1.66
--- cmCTestUpdateHandler.cxx	10 Jul 2009 15:07:57 -0000	1.67
***************
*** 33,36 ****
--- 33,37 ----
  #include "cmCTestBZR.h"
  #include "cmCTestGIT.h"
+ #include "cmCTestHG.h"
  
  #include <cmsys/auto_ptr.hxx>
***************
*** 55,59 ****
    "SVN",
    "BZR",
!   "GIT"
  };
  
--- 56,61 ----
    "SVN",
    "BZR",
!   "GIT",
!   "HG"
  };
  
***************
*** 146,149 ****
--- 148,155 ----
        return cmCTestUpdateHandler::e_GIT;
        }
+     if ( stype.find("hg") != std::string::npos )
+       {
+       return cmCTestUpdateHandler::e_HG;
+       }
      }
    else
***************
*** 168,171 ****
--- 174,181 ----
        return cmCTestUpdateHandler::e_GIT;
        }
+     if ( stype.find("hg") != std::string::npos )
+       {
+       return cmCTestUpdateHandler::e_HG;
+       }
      }
    return cmCTestUpdateHandler::e_UNKNOWN;
***************
*** 227,230 ****
--- 237,241 ----
      case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break;
      case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break;
+     case e_HG:  vc.reset(new cmCTestHG(this->CTest, ofs)); break;
      default:    vc.reset(new cmCTestVC(this->CTest, ofs));  break;
      }
***************
*** 372,375 ****
--- 383,392 ----
      return cmCTestUpdateHandler::e_GIT;
      }
+   sourceDirectory = dir;
+   sourceDirectory += "/.hg";
+   if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+     {
+     return cmCTestUpdateHandler::e_HG;
+     }
    return cmCTestUpdateHandler::e_UNKNOWN;
  }
***************
*** 401,404 ****
--- 418,422 ----
        case e_BZR: key = "BZRCommand"; break;
        case e_GIT: key = "GITCommand"; break;
+       case e_HG:  key = "HGCommand";  break;
        default: break;
        }

Index: cmCTestUpdateCommand.cxx
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v
retrieving revision 1.17
retrieving revision 1.18
diff -C 2 -d -r1.17 -r1.18
*** cmCTestUpdateCommand.cxx	14 May 2009 20:13:51 -0000	1.17
--- cmCTestUpdateCommand.cxx	10 Jul 2009 15:07:56 -0000	1.18
***************
*** 57,60 ****
--- 57,64 ----
    this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile,
      "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS");
+   this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile,
+     "HGCommand", "CTEST_HG_COMMAND");
+   this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile,
+     "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS");
  
    const char* initialCheckoutCommand

Index: cmCTestUpdateHandler.h
===================================================================
RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v
retrieving revision 1.13
retrieving revision 1.14
diff -C 2 -d -r1.13 -r1.14
*** cmCTestUpdateHandler.h	14 May 2009 20:13:51 -0000	1.13
--- cmCTestUpdateHandler.h	10 Jul 2009 15:07:58 -0000	1.14
***************
*** 49,52 ****
--- 49,53 ----
      e_BZR,
      e_GIT,
+     e_HG,
      e_LAST
    };

--- NEW FILE: cmCTestHG.cxx ---
/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile: cmCTestHG.cxx,v $
  Language:  C++
  Date:      $Date: 2009-07-10 15:07:48 $
  Version:   $Revision: 1.1 $

  Copyright (c) 2002 Kitware, Inc. All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "cmCTestHG.h"

#include "cmCTest.h"
#include "cmSystemTools.h"
#include "cmXMLParser.h"

#include <cmsys/RegularExpression.hxx>

//----------------------------------------------------------------------------
cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log):
  cmCTestGlobalVC(ct, log)
{
  this->PriorRev = this->Unknown;
}

//----------------------------------------------------------------------------
cmCTestHG::~cmCTestHG()
{
}

//----------------------------------------------------------------------------
class cmCTestHG::IdentifyParser: public cmCTestVC::LineParser
{
public:
  IdentifyParser(cmCTestHG* hg, const char* prefix,
                 std::string& rev): Rev(rev)
    {
    this->SetLog(&hg->Log, prefix);
    this->RegexIdentify.compile("^([0-9a-f]+)");
    }
private:
  std::string& Rev;
  cmsys::RegularExpression RegexIdentify;

  bool ProcessLine()
    {
    if(this->RegexIdentify.find(this->Line))
      {
      this->Rev = this->RegexIdentify.match(1);
      return false;
      }
    return true;
    }
};

//----------------------------------------------------------------------------
class cmCTestHG::StatusParser: public cmCTestVC::LineParser
{
public:
  StatusParser(cmCTestHG* hg, const char* prefix): HG(hg)
    {
    this->SetLog(&hg->Log, prefix);
    this->RegexStatus.compile("([MARC!?I]) (.*)");
    }

private:
  cmCTestHG* HG;
  cmsys::RegularExpression RegexStatus;

  bool ProcessLine()
    {
    if(this->RegexStatus.find(this->Line))
      {
      this->DoPath(this->RegexStatus.match(1)[0],
                   this->RegexStatus.match(2));
      }
    return true;
    }

  void DoPath(char status, std::string const& path)
    {
    if(path.empty()) return;

    // See "hg help status".  Note that there is no 'conflict' status.
    switch(status)
      {
      case 'M': case 'A': case '!': case 'R':
        this->HG->DoModification(PathModified, path);
        break;
      case 'I': case '?': case 'C': case ' ': default:
        break;
      }
    }
};

//----------------------------------------------------------------------------
std::string cmCTestHG::GetWorkingRevision()
{
  // Run plumbing "hg identify" to get work tree revision.
  const char* hg = this->CommandLineTool.c_str();
  const char* hg_identify[] = {hg, "identify","-i", 0};
  std::string rev;
  IdentifyParser out(this, "rev-out> ", rev);
  OutputLogger err(this->Log, "rev-err> ");
  this->RunChild(hg_identify, &out, &err);
  return rev;
}

//----------------------------------------------------------------------------
void cmCTestHG::NoteOldRevision()
{
  this->OldRevision = this->GetWorkingRevision();
  cmCTestLog(this->CTest, HANDLER_OUTPUT, "   Old revision of repository is: "
             << this->OldRevision << "\n");
  this->PriorRev.Rev = this->OldRevision;
}

//----------------------------------------------------------------------------
void cmCTestHG::NoteNewRevision()
{
  this->NewRevision = this->GetWorkingRevision();
  cmCTestLog(this->CTest, HANDLER_OUTPUT, "   New revision of repository is: "
             << this->NewRevision << "\n");
}

//----------------------------------------------------------------------------
bool cmCTestHG::UpdateImpl()
{
  // Use "hg pull" followed by "hg update" to update the working tree.
  {
  const char* hg = this->CommandLineTool.c_str();
  const char* hg_pull[] = {hg, "pull","-v", 0};
  OutputLogger out(this->Log, "pull-out> ");
  OutputLogger err(this->Log, "pull-err> ");
  this->RunChild(&hg_pull[0], &out, &err);
  }

  // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)

  std::vector<char const*> hg_update;
  hg_update.push_back(this->CommandLineTool.c_str());
  hg_update.push_back("update");
  hg_update.push_back("-v");

  // Add user-specified update options.
  std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  if(opts.empty())
    {
    opts = this->CTest->GetCTestConfiguration("HGUpdateOptions");
    }
  std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
  for(std::vector<cmStdString>::const_iterator ai = args.begin();
      ai != args.end(); ++ai)
    {
    hg_update.push_back(ai->c_str());
    }

  // Sentinel argument.
  hg_update.push_back(0);

  OutputLogger out(this->Log, "update-out> ");
  OutputLogger err(this->Log, "update-err> ");
  return this->RunUpdateCommand(&hg_update[0], &out, &err);
}

//----------------------------------------------------------------------------
class cmCTestHG::LogParser: public cmCTestVC::OutputLogger,
                            private cmXMLParser
{
public:
  LogParser(cmCTestHG* hg, const char* prefix):
    OutputLogger(hg->Log, prefix), HG(hg) { this->InitializeParser(); }
  ~LogParser() { this->CleanupParser(); }
private:
  cmCTestHG* HG;

  typedef cmCTestHG::Revision Revision;
  typedef cmCTestHG::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();
      }
    }

  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->HG->DoRevision(this->Rev, this->Changes);
      }
    else if(strcmp(name, "author") == 0 && !this->CData.empty())
      {
      this->Rev.Author.assign(&this->CData[0], this->CData.size());
      }
    else if ( strcmp(name, "email") == 0 && !this->CData.empty())
      {
      // this->Rev.Email.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());
      }
    else if(strcmp(name, "files") == 0 && !this->CData.empty())
      {
      std::vector<std::string> paths = this->SplitCData();
      for(unsigned int i = 0; i < paths.size(); ++i)
        {
        // Updated by default, will be modified using file_adds and
        // file_dels.
        this->CurChange = Change('U');
        this->CurChange.Path = paths[i];
        this->Changes.push_back(this->CurChange);
        }
      }
    else if(strcmp(name, "file_adds") == 0 && !this->CData.empty())
      {
      std::string added_paths(this->CData.begin(), this->CData.end());
      for(unsigned int i = 0; i < this->Changes.size(); ++i)
        {
        if(added_paths.find(this->Changes[i].Path) != std::string::npos)
          {
          this->Changes[i].Action = 'A';
          }
        }
      }
     else if(strcmp(name, "file_dels") == 0 && !this->CData.empty())
      {
      std::string added_paths(this->CData.begin(), this->CData.end());
      for(unsigned int i = 0; i < this->Changes.size(); ++i)
        {
        if(added_paths.find(this->Changes[i].Path) != std::string::npos)
          {
          this->Changes[i].Action = 'D';
          }
        }
      }
    this->CData.clear();
    }

  std::vector<std::string> SplitCData()
    {
    std::vector<std::string> output;
    std::string currPath;
    for(unsigned int i=0; i < this->CData.size(); ++i)
      {
      if(this->CData[i] != ' ')
        {
        currPath.push_back(this->CData[i]);
        }
      else
        {
        output.push_back(currPath);
        currPath.erase();
        }
      }
    output.push_back(currPath);
    return output;
    }

  virtual void ReportError(int, int, const char* msg)
    {
    this->HG->Log << "Error parsing hg log xml: " << msg << "\n";
    }
};

//----------------------------------------------------------------------------
void cmCTestHG::LoadRevisions()
{
  // Use 'hg log' to get revisions in a xml format.
  //
  // TODO: This should use plumbing or python code to be more precise.
  // The "list of strings" templates like {files} will not work when
  // the project has spaces in the path.  Also, they may not have
  // proper XML escapes.
  std::string range = this->OldRevision + ":" + this->NewRevision;
  const char* hg = this->CommandLineTool.c_str();
  const char* hgXMLTemplate =
    "<logentry\n"
    "   revision=\"{node|short}\">\n"
    "  <author>{author|person}</author>\n"
    "  <email>{author|email}</email>\n"
    "  <date>{date|isodate}</date>\n"
    "  <msg>{desc}</msg>\n"
    "  <files>{files}</files>\n"
    "  <file_adds>{file_adds}</file_adds>\n"
    "  <file_dels>{file_dels}</file_dels>\n"
    "</logentry>\n";
  const char* hg_log[] = {hg, "log","--removed", "-r", range.c_str(),
                          "--template", hgXMLTemplate, 0};

  LogParser out(this, "log-out> ");
  out.Process("<?xml version=\"1.0\"?>\n"
              "<log>\n");
  OutputLogger err(this->Log, "log-err> ");
  this->RunChild(hg_log, &out, &err);
  out.Process("</log>\n");
}

//----------------------------------------------------------------------------
void cmCTestHG::LoadModifications()
{
  // Use 'hg status' to get modified files.
  const char* hg = this->CommandLineTool.c_str();
  const char* hg_status[] = {hg, "status", 0};
  StatusParser out(this, "status-out> ");
  OutputLogger err(this->Log, "status-err> ");
  this->RunChild(hg_status, &out, &err);
}

--- NEW FILE: cmCTestHG.h ---
/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile: cmCTestHG.h,v $
  Language:  C++
  Date:      $Date: 2009-07-10 15:07:52 $
  Version:   $Revision: 1.1 $

  Copyright (c) 2002 Kitware, Inc. All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#ifndef cmCTestHG_h
#define cmCTestHG_h

#include "cmCTestGlobalVC.h"

/** \class cmCTestHG
 * \brief Interaction with Mercurial command-line tool
 *
 */
class cmCTestHG: public cmCTestGlobalVC
{
public:
  /** Construct with a CTest instance and update log stream.  */
  cmCTestHG(cmCTest* ctest, std::ostream& log);

  virtual ~cmCTestHG();

private:
  std::string GetWorkingRevision();
  virtual void NoteOldRevision();
  virtual void NoteNewRevision();
  virtual bool UpdateImpl();

  void LoadRevisions();
  void LoadModifications();

  // Parsing helper classes.
  class IdentifyParser;
  class StatusParser;
  class LogParser;
  friend class IdentifyParser;
  friend class StatusParser;
  friend class LogParser;
};

#endif



More information about the Cmake-commits mailing list