summaryrefslogtreecommitdiffstats
path: root/Source/CTest
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2009-07-10 15:08:05 (GMT)
committerBrad King <brad.king@kitware.com>2009-07-10 15:08:05 (GMT)
commitd4d467dbd5c4a1590333eeeb3082ad46dc9698df (patch)
tree9ade1437126f78d4d1a5d292848df45ce33cadda /Source/CTest
parentc0e8c0f5aa004fb7f8c1804a5e29bd22862c9360 (diff)
downloadCMake-d4d467dbd5c4a1590333eeeb3082ad46dc9698df.zip
CMake-d4d467dbd5c4a1590333eeeb3082ad46dc9698df.tar.gz
CMake-d4d467dbd5c4a1590333eeeb3082ad46dc9698df.tar.bz2
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.
Diffstat (limited to 'Source/CTest')
-rw-r--r--Source/CTest/cmCTestHG.cxx343
-rw-r--r--Source/CTest/cmCTestHG.h52
-rw-r--r--Source/CTest/cmCTestUpdateCommand.cxx4
-rw-r--r--Source/CTest/cmCTestUpdateHandler.cxx20
-rw-r--r--Source/CTest/cmCTestUpdateHandler.h1
5 files changed, 419 insertions, 1 deletions
diff --git a/Source/CTest/cmCTestHG.cxx b/Source/CTest/cmCTestHG.cxx
new file mode 100644
index 0000000..268fe77
--- /dev/null
+++ b/Source/CTest/cmCTestHG.cxx
@@ -0,0 +1,343 @@
+/*=========================================================================
+
+ Program: CMake - Cross-Platform Makefile Generator
+ Module: $RCSfile$
+ Language: C++
+ Date: $Date$
+ Version: $Revision$
+
+ 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);
+}
diff --git a/Source/CTest/cmCTestHG.h b/Source/CTest/cmCTestHG.h
new file mode 100644
index 0000000..aafa0e1
--- /dev/null
+++ b/Source/CTest/cmCTestHG.h
@@ -0,0 +1,52 @@
+/*=========================================================================
+
+ Program: CMake - Cross-Platform Makefile Generator
+ Module: $RCSfile$
+ Language: C++
+ Date: $Date$
+ Version: $Revision$
+
+ 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
diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx
index 73c1f40..3dc5cf6 100644
--- a/Source/CTest/cmCTestUpdateCommand.cxx
+++ b/Source/CTest/cmCTestUpdateCommand.cxx
@@ -56,6 +56,10 @@ cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler()
"GITCommand", "CTEST_GIT_COMMAND");
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
= this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND");
diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx
index d3930d7..749daa5 100644
--- a/Source/CTest/cmCTestUpdateHandler.cxx
+++ b/Source/CTest/cmCTestUpdateHandler.cxx
@@ -32,6 +32,7 @@
#include "cmCTestSVN.h"
#include "cmCTestBZR.h"
#include "cmCTestGIT.h"
+#include "cmCTestHG.h"
#include <cmsys/auto_ptr.hxx>
@@ -54,7 +55,8 @@ static const char* cmCTestUpdateHandlerUpdateStrings[] =
"CVS",
"SVN",
"BZR",
- "GIT"
+ "GIT",
+ "HG"
};
static const char* cmCTestUpdateHandlerUpdateToString(int type)
@@ -145,6 +147,10 @@ int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type)
{
return cmCTestUpdateHandler::e_GIT;
}
+ if ( stype.find("hg") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_HG;
+ }
}
else
{
@@ -167,6 +173,10 @@ int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type)
{
return cmCTestUpdateHandler::e_GIT;
}
+ if ( stype.find("hg") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_HG;
+ }
}
return cmCTestUpdateHandler::e_UNKNOWN;
}
@@ -226,6 +236,7 @@ int cmCTestUpdateHandler::ProcessHandler()
case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break;
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;
}
vc->SetCommandLineTool(this->UpdateCommand);
@@ -371,6 +382,12 @@ int cmCTestUpdateHandler::DetectVCS(const char* dir)
{
return cmCTestUpdateHandler::e_GIT;
}
+ sourceDirectory = dir;
+ sourceDirectory += "/.hg";
+ if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+ {
+ return cmCTestUpdateHandler::e_HG;
+ }
return cmCTestUpdateHandler::e_UNKNOWN;
}
@@ -400,6 +417,7 @@ bool cmCTestUpdateHandler::SelectVCS()
case e_SVN: key = "SVNCommand"; break;
case e_BZR: key = "BZRCommand"; break;
case e_GIT: key = "GITCommand"; break;
+ case e_HG: key = "HGCommand"; break;
default: break;
}
if (key)
diff --git a/Source/CTest/cmCTestUpdateHandler.h b/Source/CTest/cmCTestUpdateHandler.h
index 139ceed..f1e76f8 100644
--- a/Source/CTest/cmCTestUpdateHandler.h
+++ b/Source/CTest/cmCTestUpdateHandler.h
@@ -48,6 +48,7 @@ public:
e_SVN,
e_BZR,
e_GIT,
+ e_HG,
e_LAST
};