TZIVI-254: IVI needs a newer version of cmake
[profile/ivi/cmake.git] / Source / CTest / cmCTestHG.cxx
1 /*============================================================================
2   CMake - Cross Platform Makefile Generator
3   Copyright 2000-2009 Kitware, Inc.
4
5   Distributed under the OSI-approved BSD License (the "License");
6   see accompanying file Copyright.txt for details.
7
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"
13
14 #include "cmCTest.h"
15 #include "cmSystemTools.h"
16 #include "cmXMLParser.h"
17
18 #include <cmsys/RegularExpression.hxx>
19
20 //----------------------------------------------------------------------------
21 cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log):
22   cmCTestGlobalVC(ct, log)
23 {
24   this->PriorRev = this->Unknown;
25 }
26
27 //----------------------------------------------------------------------------
28 cmCTestHG::~cmCTestHG()
29 {
30 }
31
32 //----------------------------------------------------------------------------
33 class cmCTestHG::IdentifyParser: public cmCTestVC::LineParser
34 {
35 public:
36   IdentifyParser(cmCTestHG* hg, const char* prefix,
37                  std::string& rev): Rev(rev)
38     {
39     this->SetLog(&hg->Log, prefix);
40     this->RegexIdentify.compile("^([0-9a-f]+)");
41     }
42 private:
43   std::string& Rev;
44   cmsys::RegularExpression RegexIdentify;
45
46   bool ProcessLine()
47     {
48     if(this->RegexIdentify.find(this->Line))
49       {
50       this->Rev = this->RegexIdentify.match(1);
51       return false;
52       }
53     return true;
54     }
55 };
56
57 //----------------------------------------------------------------------------
58 class cmCTestHG::StatusParser: public cmCTestVC::LineParser
59 {
60 public:
61   StatusParser(cmCTestHG* hg, const char* prefix): HG(hg)
62     {
63     this->SetLog(&hg->Log, prefix);
64     this->RegexStatus.compile("([MARC!?I]) (.*)");
65     }
66
67 private:
68   cmCTestHG* HG;
69   cmsys::RegularExpression RegexStatus;
70
71   bool ProcessLine()
72     {
73     if(this->RegexStatus.find(this->Line))
74       {
75       this->DoPath(this->RegexStatus.match(1)[0],
76                    this->RegexStatus.match(2));
77       }
78     return true;
79     }
80
81   void DoPath(char status, std::string const& path)
82     {
83     if(path.empty()) return;
84
85     // See "hg help status".  Note that there is no 'conflict' status.
86     switch(status)
87       {
88       case 'M': case 'A': case '!': case 'R':
89         this->HG->DoModification(PathModified, path);
90         break;
91       case 'I': case '?': case 'C': case ' ': default:
92         break;
93       }
94     }
95 };
96
97 //----------------------------------------------------------------------------
98 std::string cmCTestHG::GetWorkingRevision()
99 {
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};
103   std::string rev;
104   IdentifyParser out(this, "rev-out> ", rev);
105   OutputLogger err(this->Log, "rev-err> ");
106   this->RunChild(hg_identify, &out, &err);
107   return rev;
108 }
109
110 //----------------------------------------------------------------------------
111 void cmCTestHG::NoteOldRevision()
112 {
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;
117 }
118
119 //----------------------------------------------------------------------------
120 void cmCTestHG::NoteNewRevision()
121 {
122   this->NewRevision = this->GetWorkingRevision();
123   cmCTestLog(this->CTest, HANDLER_OUTPUT, "   New revision of repository is: "
124              << this->NewRevision << "\n");
125 }
126
127 //----------------------------------------------------------------------------
128 bool cmCTestHG::UpdateImpl()
129 {
130   // Use "hg pull" followed by "hg update" to update the working tree.
131   {
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);
137   }
138
139   // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
140
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");
145
146   // Add user-specified update options.
147   std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
148   if(opts.empty())
149     {
150     opts = this->CTest->GetCTestConfiguration("HGUpdateOptions");
151     }
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)
155     {
156     hg_update.push_back(ai->c_str());
157     }
158
159   // Sentinel argument.
160   hg_update.push_back(0);
161
162   OutputLogger out(this->Log, "update-out> ");
163   OutputLogger err(this->Log, "update-err> ");
164   return this->RunUpdateCommand(&hg_update[0], &out, &err);
165 }
166
167 //----------------------------------------------------------------------------
168 class cmCTestHG::LogParser: public cmCTestVC::OutputLogger,
169                             private cmXMLParser
170 {
171 public:
172   LogParser(cmCTestHG* hg, const char* prefix):
173     OutputLogger(hg->Log, prefix), HG(hg) { this->InitializeParser(); }
174   ~LogParser() { this->CleanupParser(); }
175 private:
176   cmCTestHG* HG;
177
178   typedef cmCTestHG::Revision Revision;
179   typedef cmCTestHG::Change Change;
180   Revision Rev;
181   std::vector<Change> Changes;
182   Change CurChange;
183   std::vector<char> CData;
184
185   virtual bool ProcessChunk(const char* data, int length)
186     {
187     this->OutputLogger::ProcessChunk(data, length);
188     this->ParseChunk(data, length);
189     return true;
190     }
191
192   virtual void StartElement(const char* name, const char** atts)
193     {
194     this->CData.clear();
195     if(strcmp(name, "logentry") == 0)
196       {
197       this->Rev = Revision();
198       if(const char* rev = this->FindAttribute(atts, "revision"))
199         {
200         this->Rev.Rev = rev;
201         }
202       this->Changes.clear();
203       }
204     }
205
206   virtual void CharacterDataHandler(const char* data, int length)
207     {
208     this->CData.insert(this->CData.end(), data, data+length);
209     }
210
211   virtual void EndElement(const char* name)
212     {
213     if(strcmp(name, "logentry") == 0)
214       {
215       this->HG->DoRevision(this->Rev, this->Changes);
216       }
217     else if(strcmp(name, "author") == 0 && !this->CData.empty())
218       {
219       this->Rev.Author.assign(&this->CData[0], this->CData.size());
220       }
221     else if ( strcmp(name, "email") == 0 && !this->CData.empty())
222       {
223       this->Rev.EMail.assign(&this->CData[0], this->CData.size());
224       }
225     else if(strcmp(name, "date") == 0 && !this->CData.empty())
226       {
227       this->Rev.Date.assign(&this->CData[0], this->CData.size());
228       }
229     else if(strcmp(name, "msg") == 0 && !this->CData.empty())
230       {
231       this->Rev.Log.assign(&this->CData[0], this->CData.size());
232       }
233     else if(strcmp(name, "files") == 0 && !this->CData.empty())
234       {
235       std::vector<std::string> paths = this->SplitCData();
236       for(unsigned int i = 0; i < paths.size(); ++i)
237         {
238         // Updated by default, will be modified using file_adds and
239         // file_dels.
240         this->CurChange = Change('U');
241         this->CurChange.Path = paths[i];
242         this->Changes.push_back(this->CurChange);
243         }
244       }
245     else if(strcmp(name, "file_adds") == 0 && !this->CData.empty())
246       {
247       std::string added_paths(this->CData.begin(), this->CData.end());
248       for(unsigned int i = 0; i < this->Changes.size(); ++i)
249         {
250         if(added_paths.find(this->Changes[i].Path) != std::string::npos)
251           {
252           this->Changes[i].Action = 'A';
253           }
254         }
255       }
256      else if(strcmp(name, "file_dels") == 0 && !this->CData.empty())
257       {
258       std::string added_paths(this->CData.begin(), this->CData.end());
259       for(unsigned int i = 0; i < this->Changes.size(); ++i)
260         {
261         if(added_paths.find(this->Changes[i].Path) != std::string::npos)
262           {
263           this->Changes[i].Action = 'D';
264           }
265         }
266       }
267     this->CData.clear();
268     }
269
270   std::vector<std::string> SplitCData()
271     {
272     std::vector<std::string> output;
273     std::string currPath;
274     for(unsigned int i=0; i < this->CData.size(); ++i)
275       {
276       if(this->CData[i] != ' ')
277         {
278         currPath += this->CData[i];
279         }
280       else
281         {
282         output.push_back(currPath);
283         currPath = "";
284         }
285       }
286     output.push_back(currPath);
287     return output;
288     }
289
290   virtual void ReportError(int, int, const char* msg)
291     {
292     this->HG->Log << "Error parsing hg log xml: " << msg << "\n";
293     }
294 };
295
296 //----------------------------------------------------------------------------
297 void cmCTestHG::LoadRevisions()
298 {
299   // Use 'hg log' to get revisions in a xml format.
300   //
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 =
308     "<logentry\n"
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"
317     "</logentry>\n";
318   const char* hg_log[] = {hg, "log","--removed", "-r", range.c_str(),
319                           "--template", hgXMLTemplate, 0};
320
321   LogParser out(this, "log-out> ");
322   out.Process("<?xml version=\"1.0\"?>\n"
323               "<log>\n");
324   OutputLogger err(this->Log, "log-err> ");
325   this->RunChild(hg_log, &out, &err);
326   out.Process("</log>\n");
327 }
328
329 //----------------------------------------------------------------------------
330 void cmCTestHG::LoadModifications()
331 {
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);
338 }