ENH: check in almost building VMS stuff with VMSBuild directory since the bootstrap...
[cmake.git] / Source / CTest / cmCTestGIT.cxx
blobacd0176fc12a5b761bc3b6073f5a715b50c3442f
1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmCTestGIT.cxx,v $
5 Language: C++
6 Date: $Date: 2009-04-22 14:22:25 $
7 Version: $Revision: 1.2 $
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 "cmCTestGIT.h"
19 #include "cmCTest.h"
20 #include "cmSystemTools.h"
21 #include "cmXMLSafe.h"
23 #include <cmsys/RegularExpression.hxx>
24 #include <cmsys/ios/sstream>
25 #include <cmsys/Process.h>
27 #include <ctype.h>
29 //----------------------------------------------------------------------------
30 cmCTestGIT::cmCTestGIT(cmCTest* ct, std::ostream& log):
31 cmCTestGlobalVC(ct, log)
33 this->PriorRev = this->Unknown;
36 //----------------------------------------------------------------------------
37 cmCTestGIT::~cmCTestGIT()
41 //----------------------------------------------------------------------------
42 class cmCTestGIT::OneLineParser: public cmCTestVC::LineParser
44 public:
45 OneLineParser(cmCTestGIT* git, const char* prefix,
46 std::string& l): Line1(l)
48 this->SetLog(&git->Log, prefix);
50 private:
51 std::string& Line1;
52 virtual bool ProcessLine()
54 // Only the first line is of interest.
55 this->Line1 = this->Line;
56 return false;
60 //----------------------------------------------------------------------------
61 std::string cmCTestGIT::GetWorkingRevision()
63 // Run plumbing "git rev-list" to get work tree revision.
64 const char* git = this->CommandLineTool.c_str();
65 const char* git_rev_list[] = {git, "rev-list", "-n", "1", "HEAD", 0};
66 std::string rev;
67 OneLineParser out(this, "rl-out> ", rev);
68 OutputLogger err(this->Log, "rl-err> ");
69 this->RunChild(git_rev_list, &out, &err);
70 return rev;
73 //----------------------------------------------------------------------------
74 void cmCTestGIT::NoteOldRevision()
76 this->OldRevision = this->GetWorkingRevision();
77 cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
78 << this->OldRevision << "\n");
79 this->PriorRev.Rev = this->OldRevision;
82 //----------------------------------------------------------------------------
83 void cmCTestGIT::NoteNewRevision()
85 this->NewRevision = this->GetWorkingRevision();
86 cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
87 << this->NewRevision << "\n");
90 //----------------------------------------------------------------------------
91 bool cmCTestGIT::UpdateImpl()
93 // Use "git pull" to update the working tree.
94 std::vector<char const*> git_pull;
95 git_pull.push_back(this->CommandLineTool.c_str());
96 git_pull.push_back("pull");
98 // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
100 // Add user-specified update options.
101 std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
102 if(opts.empty())
104 opts = this->CTest->GetCTestConfiguration("GITUpdateOptions");
106 std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
107 for(std::vector<cmStdString>::const_iterator ai = args.begin();
108 ai != args.end(); ++ai)
110 git_pull.push_back(ai->c_str());
113 // Sentinel argument.
114 git_pull.push_back(0);
116 OutputLogger out(this->Log, "pull-out> ");
117 OutputLogger err(this->Log, "pull-err> ");
118 return this->RunUpdateCommand(&git_pull[0], &out, &err);
121 //----------------------------------------------------------------------------
122 /* Diff format:
124 :src-mode dst-mode src-sha1 dst-sha1 status\0
125 src-path\0
126 [dst-path\0]
128 The format is repeated for every file changed. The [dst-path\0]
129 line appears only for lines with status 'C' or 'R'. See 'git help
130 diff-tree' for details.
132 class cmCTestGIT::DiffParser: public cmCTestVC::LineParser
134 public:
135 DiffParser(cmCTestGIT* git, const char* prefix):
136 LineParser('\0', false), GIT(git), DiffField(DiffFieldNone)
138 this->SetLog(&git->Log, prefix);
141 typedef cmCTestGIT::Change Change;
142 std::vector<Change> Changes;
143 protected:
144 cmCTestGIT* GIT;
145 enum DiffFieldType { DiffFieldNone, DiffFieldChange,
146 DiffFieldSrc, DiffFieldDst };
147 DiffFieldType DiffField;
148 Change CurChange;
150 void DiffReset()
152 this->DiffField = DiffFieldNone;
153 this->Changes.clear();
156 virtual bool ProcessLine()
158 if(this->Line[0] == ':')
160 this->DiffField = DiffFieldChange;
161 this->CurChange = Change();
163 if(this->DiffField == DiffFieldChange)
165 // :src-mode dst-mode src-sha1 dst-sha1 status
166 if(this->Line[0] != ':')
168 this->DiffField = DiffFieldNone;
169 return true;
171 const char* src_mode_first = this->Line.c_str()+1;
172 const char* src_mode_last = this->ConsumeField(src_mode_first);
173 const char* dst_mode_first = this->ConsumeSpace(src_mode_last);
174 const char* dst_mode_last = this->ConsumeField(dst_mode_first);
175 const char* src_sha1_first = this->ConsumeSpace(dst_mode_last);
176 const char* src_sha1_last = this->ConsumeField(src_sha1_first);
177 const char* dst_sha1_first = this->ConsumeSpace(src_sha1_last);
178 const char* dst_sha1_last = this->ConsumeField(dst_sha1_first);
179 const char* status_first = this->ConsumeSpace(dst_sha1_last);
180 const char* status_last = this->ConsumeField(status_first);
181 if(status_first != status_last)
183 this->CurChange.Action = *status_first;
184 this->DiffField = DiffFieldSrc;
186 else
188 this->DiffField = DiffFieldNone;
191 else if(this->DiffField == DiffFieldSrc)
193 // src-path
194 if(this->CurChange.Action == 'C')
196 // Convert copy to addition of destination.
197 this->CurChange.Action = 'A';
198 this->DiffField = DiffFieldDst;
200 else if(this->CurChange.Action == 'R')
202 // Convert rename to deletion of source and addition of destination.
203 this->CurChange.Action = 'D';
204 this->CurChange.Path = this->Line;
205 this->Changes.push_back(this->CurChange);
207 this->CurChange = Change('A');
208 this->DiffField = DiffFieldDst;
210 else
212 this->CurChange.Path = this->Line;
213 this->Changes.push_back(this->CurChange);
214 this->DiffField = this->DiffFieldNone;
217 else if(this->DiffField == DiffFieldDst)
219 // dst-path
220 this->CurChange.Path = this->Line;
221 this->Changes.push_back(this->CurChange);
222 this->DiffField = this->DiffFieldNone;
224 return true;
227 const char* ConsumeSpace(const char* c)
229 while(*c && isspace(*c)) { ++c; }
230 return c;
232 const char* ConsumeField(const char* c)
234 while(*c && !isspace(*c)) { ++c; }
235 return c;
239 //----------------------------------------------------------------------------
240 /* Commit format:
242 commit ...\n
243 tree ...\n
244 parent ...\n
245 author ...\n
246 committer ...\n
248 Log message indented by (4) spaces\n
249 (even blank lines have the spaces)\n
251 [Diff format]
253 The header may have more fields. See 'git help diff-tree'.
255 class cmCTestGIT::CommitParser: public cmCTestGIT::DiffParser
257 public:
258 CommitParser(cmCTestGIT* git, const char* prefix):
259 DiffParser(git, prefix), Section(SectionHeader)
261 this->Separator = SectionSep[this->Section];
264 private:
265 typedef cmCTestGIT::Revision Revision;
266 enum SectionType { SectionHeader, SectionBody, SectionDiff, SectionCount };
267 static char const SectionSep[SectionCount];
268 SectionType Section;
269 Revision Rev;
271 struct Person
273 std::string Name;
274 std::string EMail;
275 unsigned long Time;
276 long TimeZone;
277 Person(): Name(), EMail(), Time(0), TimeZone(0) {}
280 void ParsePerson(const char* str, Person& person)
282 // Person Name <person@domain.com> 1234567890 +0000
283 const char* c = str;
284 while(*c && isspace(*c)) { ++c; }
286 const char* name_first = c;
287 while(*c && *c != '<') { ++c; }
288 const char* name_last = c;
289 while(name_last != name_first && isspace(*(name_last-1))) { --name_last; }
290 person.Name.assign(name_first, name_last-name_first);
292 const char* email_first = *c? ++c : c;
293 while(*c && *c != '>') { ++c; }
294 const char* email_last = *c? c++ : c;
295 person.EMail.assign(email_first, email_last-email_first);
297 person.Time = strtoul(c, (char**)&c, 10);
298 person.TimeZone = strtol(c, (char**)&c, 10);
301 virtual bool ProcessLine()
303 if(this->Line.empty())
305 this->NextSection();
307 else
309 switch(this->Section)
311 case SectionHeader: this->DoHeaderLine(); break;
312 case SectionBody: this->DoBodyLine(); break;
313 case SectionDiff: this->DiffParser::ProcessLine(); break;
314 case SectionCount: break; // never happens
317 return true;
320 void NextSection()
322 this->Section = SectionType((this->Section+1) % SectionCount);
323 this->Separator = SectionSep[this->Section];
324 if(this->Section == SectionHeader)
326 this->GIT->DoRevision(this->Rev, this->Changes);
327 this->Rev = Revision();
328 this->DiffReset();
332 void DoHeaderLine()
334 // Look for header fields that we need.
335 if(strncmp(this->Line.c_str(), "commit ", 7) == 0)
337 this->Rev.Rev = this->Line.c_str()+7;
339 else if(strncmp(this->Line.c_str(), "author ", 7) == 0)
341 Person author;
342 this->ParsePerson(this->Line.c_str()+7, author);
343 this->Rev.Author = author.Name;
344 char buf[1024];
345 if(author.TimeZone >= 0)
347 sprintf(buf, "%lu +%04ld", author.Time, author.TimeZone);
349 else
351 sprintf(buf, "%lu -%04ld", author.Time, -author.TimeZone);
353 this->Rev.Date = buf;
357 void DoBodyLine()
359 // Commit log lines are indented by 4 spaces.
360 if(this->Line.size() >= 4)
362 this->Rev.Log += this->Line.substr(4);
364 this->Rev.Log += "\n";
368 char const cmCTestGIT::CommitParser::SectionSep[SectionCount] =
369 {'\n', '\n', '\0'};
371 //----------------------------------------------------------------------------
372 void cmCTestGIT::LoadRevisions()
374 // Use 'git rev-list ... | git diff-tree ...' to get revisions.
375 std::string range = this->OldRevision + ".." + this->NewRevision;
376 const char* git = this->CommandLineTool.c_str();
377 const char* git_rev_list[] =
378 {git, "rev-list", "--reverse", range.c_str(), "--", 0};
379 const char* git_diff_tree[] =
380 {git, "diff-tree", "--stdin", "--always", "-z", "-r", "--pretty=raw",
381 "--encoding=utf-8", 0};
382 this->Log << this->ComputeCommandLine(git_rev_list) << " | "
383 << this->ComputeCommandLine(git_diff_tree) << "\n";
385 cmsysProcess* cp = cmsysProcess_New();
386 cmsysProcess_AddCommand(cp, git_rev_list);
387 cmsysProcess_AddCommand(cp, git_diff_tree);
388 cmsysProcess_SetWorkingDirectory(cp, this->SourceDirectory.c_str());
390 CommitParser out(this, "dt-out> ");
391 OutputLogger err(this->Log, "dt-err> ");
392 this->RunProcess(cp, &out, &err);
394 // Send one extra zero-byte to terminate the last record.
395 out.Process("", 1);
397 cmsysProcess_Delete(cp);
400 //----------------------------------------------------------------------------
401 void cmCTestGIT::LoadModifications()
403 // Use 'git diff-index' to get modified files.
404 const char* git = this->CommandLineTool.c_str();
405 const char* git_diff_index[] = {git, "diff-index", "-z", "HEAD", 0};
407 DiffParser out(this, "di-out> ");
408 OutputLogger err(this->Log, "di-err> ");
409 this->RunChild(git_diff_index, &out, &err);
411 for(std::vector<Change>::const_iterator ci = out.Changes.begin();
412 ci != out.Changes.end(); ++ci)
414 this->DoModification(PathModified, ci->Path);