1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmCTestGIT.cxx,v $
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"
20 #include "cmSystemTools.h"
21 #include "cmXMLSafe.h"
23 #include <cmsys/RegularExpression.hxx>
24 #include <cmsys/ios/sstream>
25 #include <cmsys/Process.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
45 OneLineParser(cmCTestGIT
* git
, const char* prefix
,
46 std::string
& l
): Line1(l
)
48 this->SetLog(&git
->Log
, prefix
);
52 virtual bool ProcessLine()
54 // Only the first line is of interest.
55 this->Line1
= this->Line
;
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};
67 OneLineParser
out(this, "rl-out> ", rev
);
68 OutputLogger
err(this->Log
, "rl-err> ");
69 this->RunChild(git_rev_list
, &out
, &err
);
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");
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 //----------------------------------------------------------------------------
124 :src-mode dst-mode src-sha1 dst-sha1 status\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
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
;
145 enum DiffFieldType
{ DiffFieldNone
, DiffFieldChange
,
146 DiffFieldSrc
, DiffFieldDst
};
147 DiffFieldType DiffField
;
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
;
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
;
188 this->DiffField
= DiffFieldNone
;
191 else if(this->DiffField
== DiffFieldSrc
)
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
;
212 this->CurChange
.Path
= this->Line
;
213 this->Changes
.push_back(this->CurChange
);
214 this->DiffField
= this->DiffFieldNone
;
217 else if(this->DiffField
== DiffFieldDst
)
220 this->CurChange
.Path
= this->Line
;
221 this->Changes
.push_back(this->CurChange
);
222 this->DiffField
= this->DiffFieldNone
;
227 const char* ConsumeSpace(const char* c
)
229 while(*c
&& isspace(*c
)) { ++c
; }
232 const char* ConsumeField(const char* c
)
234 while(*c
&& !isspace(*c
)) { ++c
; }
239 //----------------------------------------------------------------------------
248 Log message indented by (4) spaces\n
249 (even blank lines have the spaces)\n
253 The header may have more fields. See 'git help diff-tree'.
255 class cmCTestGIT::CommitParser
: public cmCTestGIT::DiffParser
258 CommitParser(cmCTestGIT
* git
, const char* prefix
):
259 DiffParser(git
, prefix
), Section(SectionHeader
)
261 this->Separator
= SectionSep
[this->Section
];
265 typedef cmCTestGIT::Revision Revision
;
266 enum SectionType
{ SectionHeader
, SectionBody
, SectionDiff
, SectionCount
};
267 static char const SectionSep
[SectionCount
];
277 Person(): Name(), EMail(), Time(0), TimeZone(0) {}
280 void ParsePerson(const char* str
, Person
& person
)
282 // Person Name <person@domain.com> 1234567890 +0000
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())
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
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();
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)
342 this->ParsePerson(this->Line
.c_str()+7, author
);
343 this->Rev
.Author
= author
.Name
;
345 if(author
.TimeZone
>= 0)
347 sprintf(buf
, "%lu +%04ld", author
.Time
, author
.TimeZone
);
351 sprintf(buf
, "%lu -%04ld", author
.Time
, -author
.TimeZone
);
353 this->Rev
.Date
= buf
;
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
] =
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.
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
);