1 /*============================================================================
2 CMake - Cross Platform Makefile Generator
3 Copyright 2000-2009 Kitware, Inc.
5 Distributed under the OSI-approved BSD License (the "License");
6 see accompanying file Copyright.txt for details.
8 This software is distributed WITHOUT ANY WARRANTY; without even the
9 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 See the License for more information.
11 ============================================================================*/
12 #include "cmCTestHG.h"
15 #include "cmSystemTools.h"
16 #include "cmXMLParser.h"
18 #include <cmsys/RegularExpression.hxx>
20 //----------------------------------------------------------------------------
21 cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log):
22 cmCTestGlobalVC(ct, log)
24 this->PriorRev = this->Unknown;
27 //----------------------------------------------------------------------------
28 cmCTestHG::~cmCTestHG()
32 //----------------------------------------------------------------------------
33 class cmCTestHG::IdentifyParser: public cmCTestVC::LineParser
36 IdentifyParser(cmCTestHG* hg, const char* prefix,
37 std::string& rev): Rev(rev)
39 this->SetLog(&hg->Log, prefix);
40 this->RegexIdentify.compile("^([0-9a-f]+)");
44 cmsys::RegularExpression RegexIdentify;
48 if(this->RegexIdentify.find(this->Line))
50 this->Rev = this->RegexIdentify.match(1);
57 //----------------------------------------------------------------------------
58 class cmCTestHG::StatusParser: public cmCTestVC::LineParser
61 StatusParser(cmCTestHG* hg, const char* prefix): HG(hg)
63 this->SetLog(&hg->Log, prefix);
64 this->RegexStatus.compile("([MARC!?I]) (.*)");
69 cmsys::RegularExpression RegexStatus;
73 if(this->RegexStatus.find(this->Line))
75 this->DoPath(this->RegexStatus.match(1)[0],
76 this->RegexStatus.match(2));
81 void DoPath(char status, std::string const& path)
83 if(path.empty()) return;
85 // See "hg help status". Note that there is no 'conflict' status.
88 case 'M': case 'A': case '!': case 'R':
89 this->HG->DoModification(PathModified, path);
91 case 'I': case '?': case 'C': case ' ': default:
97 //----------------------------------------------------------------------------
98 std::string cmCTestHG::GetWorkingRevision()
100 // Run plumbing "hg identify" to get work tree revision.
101 const char* hg = this->CommandLineTool.c_str();
102 const char* hg_identify[] = {hg, "identify","-i", 0};
104 IdentifyParser out(this, "rev-out> ", rev);
105 OutputLogger err(this->Log, "rev-err> ");
106 this->RunChild(hg_identify, &out, &err);
110 //----------------------------------------------------------------------------
111 void cmCTestHG::NoteOldRevision()
113 this->OldRevision = this->GetWorkingRevision();
114 cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
115 << this->OldRevision << "\n");
116 this->PriorRev.Rev = this->OldRevision;
119 //----------------------------------------------------------------------------
120 void cmCTestHG::NoteNewRevision()
122 this->NewRevision = this->GetWorkingRevision();
123 cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
124 << this->NewRevision << "\n");
127 //----------------------------------------------------------------------------
128 bool cmCTestHG::UpdateImpl()
130 // Use "hg pull" followed by "hg update" to update the working tree.
132 const char* hg = this->CommandLineTool.c_str();
133 const char* hg_pull[] = {hg, "pull","-v", 0};
134 OutputLogger out(this->Log, "pull-out> ");
135 OutputLogger err(this->Log, "pull-err> ");
136 this->RunChild(&hg_pull[0], &out, &err);
139 // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
141 std::vector<char const*> hg_update;
142 hg_update.push_back(this->CommandLineTool.c_str());
143 hg_update.push_back("update");
144 hg_update.push_back("-v");
146 // Add user-specified update options.
147 std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
150 opts = this->CTest->GetCTestConfiguration("HGUpdateOptions");
152 std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
153 for(std::vector<cmStdString>::const_iterator ai = args.begin();
154 ai != args.end(); ++ai)
156 hg_update.push_back(ai->c_str());
159 // Sentinel argument.
160 hg_update.push_back(0);
162 OutputLogger out(this->Log, "update-out> ");
163 OutputLogger err(this->Log, "update-err> ");
164 return this->RunUpdateCommand(&hg_update[0], &out, &err);
167 //----------------------------------------------------------------------------
168 class cmCTestHG::LogParser: public cmCTestVC::OutputLogger,
172 LogParser(cmCTestHG* hg, const char* prefix):
173 OutputLogger(hg->Log, prefix), HG(hg) { this->InitializeParser(); }
174 ~LogParser() { this->CleanupParser(); }
178 typedef cmCTestHG::Revision Revision;
179 typedef cmCTestHG::Change Change;
181 std::vector<Change> Changes;
183 std::vector<char> CData;
185 virtual bool ProcessChunk(const char* data, int length)
187 this->OutputLogger::ProcessChunk(data, length);
188 this->ParseChunk(data, length);
192 virtual void StartElement(const char* name, const char** atts)
195 if(strcmp(name, "logentry") == 0)
197 this->Rev = Revision();
198 if(const char* rev = this->FindAttribute(atts, "revision"))
202 this->Changes.clear();
206 virtual void CharacterDataHandler(const char* data, int length)
208 this->CData.insert(this->CData.end(), data, data+length);
211 virtual void EndElement(const char* name)
213 if(strcmp(name, "logentry") == 0)
215 this->HG->DoRevision(this->Rev, this->Changes);
217 else if(strcmp(name, "author") == 0 && !this->CData.empty())
219 this->Rev.Author.assign(&this->CData[0], this->CData.size());
221 else if ( strcmp(name, "email") == 0 && !this->CData.empty())
223 this->Rev.EMail.assign(&this->CData[0], this->CData.size());
225 else if(strcmp(name, "date") == 0 && !this->CData.empty())
227 this->Rev.Date.assign(&this->CData[0], this->CData.size());
229 else if(strcmp(name, "msg") == 0 && !this->CData.empty())
231 this->Rev.Log.assign(&this->CData[0], this->CData.size());
233 else if(strcmp(name, "files") == 0 && !this->CData.empty())
235 std::vector<std::string> paths = this->SplitCData();
236 for(unsigned int i = 0; i < paths.size(); ++i)
238 // Updated by default, will be modified using file_adds and
240 this->CurChange = Change('U');
241 this->CurChange.Path = paths[i];
242 this->Changes.push_back(this->CurChange);
245 else if(strcmp(name, "file_adds") == 0 && !this->CData.empty())
247 std::string added_paths(this->CData.begin(), this->CData.end());
248 for(unsigned int i = 0; i < this->Changes.size(); ++i)
250 if(added_paths.find(this->Changes[i].Path) != std::string::npos)
252 this->Changes[i].Action = 'A';
256 else if(strcmp(name, "file_dels") == 0 && !this->CData.empty())
258 std::string added_paths(this->CData.begin(), this->CData.end());
259 for(unsigned int i = 0; i < this->Changes.size(); ++i)
261 if(added_paths.find(this->Changes[i].Path) != std::string::npos)
263 this->Changes[i].Action = 'D';
270 std::vector<std::string> SplitCData()
272 std::vector<std::string> output;
273 std::string currPath;
274 for(unsigned int i=0; i < this->CData.size(); ++i)
276 if(this->CData[i] != ' ')
278 currPath += this->CData[i];
282 output.push_back(currPath);
286 output.push_back(currPath);
290 virtual void ReportError(int, int, const char* msg)
292 this->HG->Log << "Error parsing hg log xml: " << msg << "\n";
296 //----------------------------------------------------------------------------
297 void cmCTestHG::LoadRevisions()
299 // Use 'hg log' to get revisions in a xml format.
301 // TODO: This should use plumbing or python code to be more precise.
302 // The "list of strings" templates like {files} will not work when
303 // the project has spaces in the path. Also, they may not have
304 // proper XML escapes.
305 std::string range = this->OldRevision + ":" + this->NewRevision;
306 const char* hg = this->CommandLineTool.c_str();
307 const char* hgXMLTemplate =
309 " revision=\"{node|short}\">\n"
310 " <author>{author|person}</author>\n"
311 " <email>{author|email}</email>\n"
312 " <date>{date|isodate}</date>\n"
313 " <msg>{desc}</msg>\n"
314 " <files>{files}</files>\n"
315 " <file_adds>{file_adds}</file_adds>\n"
316 " <file_dels>{file_dels}</file_dels>\n"
318 const char* hg_log[] = {hg, "log","--removed", "-r", range.c_str(),
319 "--template", hgXMLTemplate, 0};
321 LogParser out(this, "log-out> ");
322 out.Process("<?xml version=\"1.0\"?>\n"
324 OutputLogger err(this->Log, "log-err> ");
325 this->RunChild(hg_log, &out, &err);
326 out.Process("</log>\n");
329 //----------------------------------------------------------------------------
330 void cmCTestHG::LoadModifications()
332 // Use 'hg status' to get modified files.
333 const char* hg = this->CommandLineTool.c_str();
334 const char* hg_status[] = {hg, "status", 0};
335 StatusParser out(this, "status-out> ");
336 OutputLogger err(this->Log, "status-err> ");
337 this->RunChild(hg_status, &out, &err);