Resync
[CMakeLuaTailorHgBridge.git] / CMakeLua / Source / CTest / cmCTestSVN.cxx
blobdf32323dd9a087101c6a6e7be1e9871b7e9d0cb7
1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmCTestSVN.cxx,v $
5 Language: C++
6 Date: $Date: 2009-02-25 20:45:14 $
7 Version: $Revision: 1.6 $
9 Copyright (c) 2002 Kitware, Inc. All rights reserved.
10 See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
12 This software is distributed WITHOUT ANY WARRANTY; without even
13 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 PURPOSE. See the above copyright notices for more information.
16 =========================================================================*/
17 #include "cmCTestSVN.h"
19 #include "cmCTest.h"
20 #include "cmSystemTools.h"
21 #include "cmXMLParser.h"
22 #include "cmXMLSafe.h"
24 #include <cmsys/RegularExpression.hxx>
26 //----------------------------------------------------------------------------
27 cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log)
29 this->PriorRev = this->Unknown;
32 //----------------------------------------------------------------------------
33 cmCTestSVN::~cmCTestSVN()
37 //----------------------------------------------------------------------------
38 void cmCTestSVN::CleanupImpl()
40 const char* svn = this->CommandLineTool.c_str();
41 const char* svn_cleanup[] = {svn, "cleanup", 0};
42 OutputLogger out(this->Log, "cleanup-out> ");
43 OutputLogger err(this->Log, "cleanup-err> ");
44 this->RunChild(svn_cleanup, &out, &err);
47 //----------------------------------------------------------------------------
48 class cmCTestSVN::InfoParser: public cmCTestVC::LineParser
50 public:
51 InfoParser(cmCTestSVN* svn, const char* prefix, std::string& rev):
52 SVN(svn), Rev(rev)
54 this->SetLog(&svn->Log, prefix);
55 this->RegexRev.compile("^Revision: ([0-9]+)");
56 this->RegexURL.compile("^URL: +([^ ]+) *$");
57 this->RegexRoot.compile("^Repository Root: +([^ ]+) *$");
59 private:
60 cmCTestSVN* SVN;
61 std::string& Rev;
62 cmsys::RegularExpression RegexRev;
63 cmsys::RegularExpression RegexURL;
64 cmsys::RegularExpression RegexRoot;
65 virtual bool ProcessLine()
67 if(this->RegexRev.find(this->Line))
69 this->Rev = this->RegexRev.match(1);
71 else if(this->RegexURL.find(this->Line))
73 this->SVN->URL = this->RegexURL.match(1);
75 else if(this->RegexRoot.find(this->Line))
77 this->SVN->Root = this->RegexRoot.match(1);
79 return true;
83 //----------------------------------------------------------------------------
84 static bool cmCTestSVNPathStarts(std::string const& p1, std::string const& p2)
86 // Does path p1 start with path p2?
87 if(p1.size() == p2.size())
89 return p1 == p2;
91 else if(p1.size() > p2.size() && p1[p2.size()] == '/')
93 return strncmp(p1.c_str(), p2.c_str(), p2.size()) == 0;
95 else
97 return false;
101 //----------------------------------------------------------------------------
102 std::string cmCTestSVN::LoadInfo()
104 // Run "svn info" to get the repository info from the work tree.
105 const char* svn = this->CommandLineTool.c_str();
106 const char* svn_info[] = {svn, "info", 0};
107 std::string rev;
108 InfoParser out(this, "info-out> ", rev);
109 OutputLogger err(this->Log, "info-err> ");
110 this->RunChild(svn_info, &out, &err);
111 return rev;
114 //----------------------------------------------------------------------------
115 void cmCTestSVN::NoteOldRevision()
117 this->OldRevision = this->LoadInfo();
118 this->Log << "Revision before update: " << this->OldRevision << "\n";
119 cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
120 << this->OldRevision << "\n");
121 this->PriorRev.Rev = this->OldRevision;
124 //----------------------------------------------------------------------------
125 void cmCTestSVN::NoteNewRevision()
127 this->NewRevision = this->LoadInfo();
128 this->Log << "Revision after update: " << this->NewRevision << "\n";
129 cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
130 << this->NewRevision << "\n");
132 // this->Root = ""; // uncomment to test GuessBase
133 this->Log << "URL = " << this->URL << "\n";
134 this->Log << "Root = " << this->Root << "\n";
136 // Compute the base path the working tree has checked out under
137 // the repository root.
138 if(!this->Root.empty() && cmCTestSVNPathStarts(this->URL, this->Root))
140 this->Base = cmCTest::DecodeURL(this->URL.substr(this->Root.size()));
141 this->Base += "/";
143 this->Log << "Base = " << this->Base << "\n";
146 //----------------------------------------------------------------------------
147 void cmCTestSVN::GuessBase(std::vector<Change> const& changes)
149 // Subversion did not give us a good repository root so we need to
150 // guess the base path from the URL and the paths in a revision with
151 // changes under it.
153 // Consider each possible URL suffix from longest to shortest.
154 for(std::string::size_type slash = this->URL.find('/');
155 this->Base.empty() && slash != std::string::npos;
156 slash = this->URL.find('/', slash+1))
158 // If the URL suffix is a prefix of at least one path then it is the base.
159 std::string base = cmCTest::DecodeURL(this->URL.substr(slash));
160 for(std::vector<Change>::const_iterator ci = changes.begin();
161 this->Base.empty() && ci != changes.end(); ++ci)
163 if(cmCTestSVNPathStarts(ci->Path, base))
165 this->Base = base;
170 // We always append a slash so that we know paths beginning in the
171 // base lie under its path. If no base was found then the working
172 // tree must be a checkout of the entire repo and this will match
173 // the leading slash in all paths.
174 this->Base += "/";
176 this->Log << "Guessed Base = " << this->Base << "\n";
179 //----------------------------------------------------------------------------
180 const char* cmCTestSVN::LocalPath(std::string const& path)
182 if(path.size() > this->Base.size() &&
183 strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0)
185 // This path lies under the base, so return a relative path.
186 return path.c_str() + this->Base.size();
188 else
190 // This path does not lie under the base, so ignore it.
191 return 0;
195 //----------------------------------------------------------------------------
196 class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser
198 public:
199 UpdateParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
201 this->SetLog(&svn->Log, prefix);
202 this->RegexUpdate.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$");
204 private:
205 cmCTestSVN* SVN;
206 cmsys::RegularExpression RegexUpdate;
208 bool ProcessLine()
210 if(this->RegexUpdate.find(this->Line))
212 this->DoPath(this->RegexUpdate.match(1)[0],
213 this->RegexUpdate.match(2)[0],
214 this->RegexUpdate.match(3));
216 return true;
219 void DoPath(char path_status, char prop_status, std::string const& path)
221 char status = (path_status != ' ')? path_status : prop_status;
222 std::string dir = cmSystemTools::GetFilenamePath(path);
223 std::string name = cmSystemTools::GetFilenameName(path);
224 // See "svn help update".
225 switch(status)
227 case 'G':
228 this->SVN->Dirs[dir][name].Status = PathModified;
229 break;
230 case 'C':
231 this->SVN->Dirs[dir][name].Status = PathConflicting;
232 break;
233 case 'A': case 'D': case 'U':
234 this->SVN->Dirs[dir][name].Status = PathUpdated;
235 break;
236 case 'E': // TODO?
237 case '?': case ' ': default:
238 break;
243 //----------------------------------------------------------------------------
244 bool cmCTestSVN::UpdateImpl()
246 // Get user-specified update options.
247 std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
248 if(opts.empty())
250 opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions");
252 std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
254 // Specify the start time for nightly testing.
255 if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
257 args.push_back("-r{" + this->GetNightlyTime() + " +0000}");
260 std::vector<char const*> svn_update;
261 svn_update.push_back(this->CommandLineTool.c_str());
262 svn_update.push_back("update");
263 svn_update.push_back("--non-interactive");
264 for(std::vector<cmStdString>::const_iterator ai = args.begin();
265 ai != args.end(); ++ai)
267 svn_update.push_back(ai->c_str());
269 svn_update.push_back(0);
271 UpdateParser out(this, "up-out> ");
272 OutputLogger err(this->Log, "up-err> ");
273 return this->RunUpdateCommand(&svn_update[0], &out, &err);
276 //----------------------------------------------------------------------------
277 class cmCTestSVN::LogParser: public cmCTestVC::OutputLogger,
278 private cmXMLParser
280 public:
281 LogParser(cmCTestSVN* svn, const char* prefix):
282 OutputLogger(svn->Log, prefix), SVN(svn) { this->InitializeParser(); }
283 ~LogParser() { this->CleanupParser(); }
284 private:
285 cmCTestSVN* SVN;
287 typedef cmCTestSVN::Revision Revision;
288 typedef cmCTestSVN::Change Change;
289 Revision Rev;
290 std::vector<Change> Changes;
291 Change CurChange;
292 std::vector<char> CData;
294 virtual bool ProcessChunk(const char* data, int length)
296 this->OutputLogger::ProcessChunk(data, length);
297 this->ParseChunk(data, length);
298 return true;
301 virtual void StartElement(const char* name, const char** atts)
303 this->CData.clear();
304 if(strcmp(name, "logentry") == 0)
306 this->Rev = Revision();
307 if(const char* rev = this->FindAttribute(atts, "revision"))
309 this->Rev.Rev = rev;
311 this->Changes.clear();
313 else if(strcmp(name, "path") == 0)
315 this->CurChange = Change();
316 if(const char* action = this->FindAttribute(atts, "action"))
318 this->CurChange.Action = action[0];
323 virtual void CharacterDataHandler(const char* data, int length)
325 this->CData.insert(this->CData.end(), data, data+length);
328 virtual void EndElement(const char* name)
330 if(strcmp(name, "logentry") == 0)
332 this->SVN->DoRevision(this->Rev, this->Changes);
334 else if(strcmp(name, "path") == 0 && !this->CData.empty())
336 this->CurChange.Path.assign(&this->CData[0], this->CData.size());
337 this->Changes.push_back(this->CurChange);
339 else if(strcmp(name, "author") == 0 && !this->CData.empty())
341 this->Rev.Author.assign(&this->CData[0], this->CData.size());
343 else if(strcmp(name, "date") == 0 && !this->CData.empty())
345 this->Rev.Date.assign(&this->CData[0], this->CData.size());
347 else if(strcmp(name, "msg") == 0 && !this->CData.empty())
349 this->Rev.Log.assign(&this->CData[0], this->CData.size());
351 this->CData.clear();
354 virtual void ReportError(int, int, const char* msg)
356 this->SVN->Log << "Error parsing svn log xml: " << msg << "\n";
360 //----------------------------------------------------------------------------
361 void cmCTestSVN::LoadRevisions()
363 cmCTestLog(this->CTest, HANDLER_OUTPUT,
364 " Gathering version information (one . per revision):\n"
365 " " << std::flush);
367 // We are interested in every revision included in the update.
368 std::string revs;
369 if(atoi(this->OldRevision.c_str()) < atoi(this->NewRevision.c_str()))
371 revs = "-r" + this->OldRevision + ":" + this->NewRevision;
373 else
375 revs = "-r" + this->NewRevision;
378 // Run "svn log" to get all global revisions of interest.
379 const char* svn = this->CommandLineTool.c_str();
380 const char* svn_log[] = {svn, "log", "--xml", "-v", revs.c_str(), 0};
382 LogParser out(this, "log-out> ");
383 OutputLogger err(this->Log, "log-err> ");
384 this->RunChild(svn_log, &out, &err);
386 cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);
389 //----------------------------------------------------------------------------
390 void cmCTestSVN::DoRevision(Revision const& revision,
391 std::vector<Change> const& changes)
393 // Guess the base checkout path from the changes if necessary.
394 if(this->Base.empty() && !changes.empty())
396 this->GuessBase(changes);
399 // Indicate we found a revision.
400 cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush);
402 // Ignore changes in the old revision.
403 if(revision.Rev == this->OldRevision)
405 this->PriorRev = revision;
406 return;
409 // Store the revision.
410 this->Revisions.push_back(revision);
412 // Report this revision.
413 Revision const& rev = this->Revisions.back();
414 this->Log << "Found revision " << rev.Rev << "\n"
415 << " author = " << rev.Author << "\n"
416 << " date = " << rev.Date << "\n";
418 // Update information about revisions of the changed files.
419 for(std::vector<Change>::const_iterator ci = changes.begin();
420 ci != changes.end(); ++ci)
422 if(const char* local = this->LocalPath(ci->Path))
424 std::string dir = cmSystemTools::GetFilenamePath(local);
425 std::string name = cmSystemTools::GetFilenameName(local);
426 File& file = this->Dirs[dir][name];
427 file.PriorRev = file.Rev? file.Rev : &this->PriorRev;
428 file.Rev = &rev;
429 this->Log << " " << ci->Action << " " << local << " " << "\n";
434 //----------------------------------------------------------------------------
435 class cmCTestSVN::StatusParser: public cmCTestVC::LineParser
437 public:
438 StatusParser(cmCTestSVN* svn, const char* prefix): SVN(svn)
440 this->SetLog(&svn->Log, prefix);
441 this->RegexStatus.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$");
443 private:
444 cmCTestSVN* SVN;
445 cmsys::RegularExpression RegexStatus;
446 bool ProcessLine()
448 if(this->RegexStatus.find(this->Line))
450 this->DoPath(this->RegexStatus.match(1)[0],
451 this->RegexStatus.match(2)[0],
452 this->RegexStatus.match(3));
454 return true;
457 void DoPath(char path_status, char prop_status, std::string const& path)
459 char status = (path_status != ' ')? path_status : prop_status;
460 // See "svn help status".
461 switch(status)
463 case 'M': case '!': case 'A': case 'D': case 'R': case 'X':
464 this->DoPath(PathModified, path);
465 break;
466 case 'C': case '~':
467 this->DoPath(PathConflicting, path);
468 break;
469 case 'I': case '?': case ' ': default:
470 break;
474 void DoPath(PathStatus status, std::string const& path)
476 std::string dir = cmSystemTools::GetFilenamePath(path);
477 std::string name = cmSystemTools::GetFilenameName(path);
478 File& file = this->SVN->Dirs[dir][name];
479 file.Status = status;
480 // For local modifications the current rev is unknown and the
481 // prior rev is the latest from svn.
482 if(!file.Rev && !file.PriorRev)
484 file.PriorRev = &this->SVN->PriorRev;
489 //----------------------------------------------------------------------------
490 void cmCTestSVN::LoadModifications()
492 // Run "svn status" which reports local modifications.
493 const char* svn = this->CommandLineTool.c_str();
494 const char* svn_status[] = {svn, "status", "--non-interactive", 0};
495 StatusParser out(this, "status-out> ");
496 OutputLogger err(this->Log, "status-err> ");
497 this->RunChild(svn_status, &out, &err);
500 //----------------------------------------------------------------------------
501 void cmCTestSVN::WriteXMLDirectory(std::ostream& xml,
502 std::string const& path,
503 Directory const& dir)
505 const char* slash = path.empty()? "":"/";
506 xml << "\t<Directory>\n"
507 << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\n";
508 for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi)
510 std::string full = path + slash + fi->first;
511 this->WriteXMLEntry(xml, path, fi->first, full, fi->second);
513 xml << "\t</Directory>\n";
516 //----------------------------------------------------------------------------
517 bool cmCTestSVN::WriteXMLUpdates(std::ostream& xml)
519 this->LoadRevisions();
520 this->LoadModifications();
522 for(std::map<cmStdString, Directory>::const_iterator
523 di = this->Dirs.begin(); di != this->Dirs.end(); ++di)
525 this->WriteXMLDirectory(xml, di->first, di->second);
528 return true;