1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmCTestSVN.cxx,v $
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"
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
51 InfoParser(cmCTestSVN
* svn
, const char* prefix
, std::string
& 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: +([^ ]+) *$");
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);
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())
91 else if(p1
.size() > p2
.size() && p1
[p2
.size()] == '/')
93 return strncmp(p1
.c_str(), p2
.c_str(), p2
.size()) == 0;
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};
108 InfoParser
out(this, "info-out> ", rev
);
109 OutputLogger
err(this->Log
, "info-err> ");
110 this->RunChild(svn_info
, &out
, &err
);
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()));
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
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
))
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.
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();
190 // This path does not lie under the base, so ignore it.
195 //----------------------------------------------------------------------------
196 class cmCTestSVN::UpdateParser
: public cmCTestVC::LineParser
199 UpdateParser(cmCTestSVN
* svn
, const char* prefix
): SVN(svn
)
201 this->SetLog(&svn
->Log
, prefix
);
202 this->RegexUpdate
.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$");
206 cmsys::RegularExpression RegexUpdate
;
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));
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".
228 this->SVN
->Dirs
[dir
][name
].Status
= PathModified
;
231 this->SVN
->Dirs
[dir
][name
].Status
= PathConflicting
;
233 case 'A': case 'D': case 'U':
234 this->SVN
->Dirs
[dir
][name
].Status
= PathUpdated
;
237 case '?': case ' ': default:
243 //----------------------------------------------------------------------------
244 bool cmCTestSVN::UpdateImpl()
246 // Get user-specified update options.
247 std::string opts
= this->CTest
->GetCTestConfiguration("UpdateOptions");
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
,
281 LogParser(cmCTestSVN
* svn
, const char* prefix
):
282 OutputLogger(svn
->Log
, prefix
), SVN(svn
) { this->InitializeParser(); }
283 ~LogParser() { this->CleanupParser(); }
287 typedef cmCTestSVN::Revision Revision
;
288 typedef cmCTestSVN::Change Change
;
290 std::vector
<Change
> Changes
;
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
);
301 virtual void StartElement(const char* name
, const char** atts
)
304 if(strcmp(name
, "logentry") == 0)
306 this->Rev
= Revision();
307 if(const char* rev
= this->FindAttribute(atts
, "revision"))
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());
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"
367 // We are interested in every revision included in the update.
369 if(atoi(this->OldRevision
.c_str()) < atoi(this->NewRevision
.c_str()))
371 revs
= "-r" + this->OldRevision
+ ":" + this->NewRevision
;
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
;
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
;
429 this->Log
<< " " << ci
->Action
<< " " << local
<< " " << "\n";
434 //----------------------------------------------------------------------------
435 class cmCTestSVN::StatusParser
: public cmCTestVC::LineParser
438 StatusParser(cmCTestSVN
* svn
, const char* prefix
): SVN(svn
)
440 this->SetLog(&svn
->Log
, prefix
);
441 this->RegexStatus
.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$");
445 cmsys::RegularExpression RegexStatus
;
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));
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".
463 case 'M': case '!': case 'A': case 'D': case 'R': case 'X':
464 this->DoPath(PathModified
, path
);
467 this->DoPath(PathConflicting
, path
);
469 case 'I': case '?': case ' ': default:
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
);