cmCTestGIT.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. /*=========================================================================
  2. Program: CMake - Cross-Platform Makefile Generator
  3. Module: $RCSfile$
  4. Language: C++
  5. Date: $Date$
  6. Version: $Revision$
  7. Copyright (c) 2002 Kitware, Inc. All rights reserved.
  8. See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
  9. This software is distributed WITHOUT ANY WARRANTY; without even
  10. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  11. PURPOSE. See the above copyright notices for more information.
  12. =========================================================================*/
  13. #include "cmCTestGIT.h"
  14. #include "cmCTest.h"
  15. #include "cmSystemTools.h"
  16. #include "cmXMLSafe.h"
  17. #include <cmsys/RegularExpression.hxx>
  18. #include <cmsys/ios/sstream>
  19. #include <cmsys/Process.h>
  20. #include <ctype.h>
  21. //----------------------------------------------------------------------------
  22. cmCTestGIT::cmCTestGIT(cmCTest* ct, std::ostream& log):
  23. cmCTestGlobalVC(ct, log)
  24. {
  25. this->PriorRev = this->Unknown;
  26. }
  27. //----------------------------------------------------------------------------
  28. cmCTestGIT::~cmCTestGIT()
  29. {
  30. }
  31. //----------------------------------------------------------------------------
  32. class cmCTestGIT::OneLineParser: public cmCTestVC::LineParser
  33. {
  34. public:
  35. OneLineParser(cmCTestGIT* git, const char* prefix,
  36. std::string& l): Line1(l)
  37. {
  38. this->SetLog(&git->Log, prefix);
  39. }
  40. private:
  41. std::string& Line1;
  42. virtual bool ProcessLine()
  43. {
  44. // Only the first line is of interest.
  45. this->Line1 = this->Line;
  46. return false;
  47. }
  48. };
  49. //----------------------------------------------------------------------------
  50. std::string cmCTestGIT::GetWorkingRevision()
  51. {
  52. // Run plumbing "git rev-list" to get work tree revision.
  53. const char* git = this->CommandLineTool.c_str();
  54. const char* git_rev_list[] = {git, "rev-list", "-n", "1", "HEAD", 0};
  55. std::string rev;
  56. OneLineParser out(this, "rl-out> ", rev);
  57. OutputLogger err(this->Log, "rl-err> ");
  58. this->RunChild(git_rev_list, &out, &err);
  59. return rev;
  60. }
  61. //----------------------------------------------------------------------------
  62. void cmCTestGIT::NoteOldRevision()
  63. {
  64. this->OldRevision = this->GetWorkingRevision();
  65. cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
  66. << this->OldRevision << "\n");
  67. this->PriorRev.Rev = this->OldRevision;
  68. }
  69. //----------------------------------------------------------------------------
  70. void cmCTestGIT::NoteNewRevision()
  71. {
  72. this->NewRevision = this->GetWorkingRevision();
  73. cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
  74. << this->NewRevision << "\n");
  75. }
  76. //----------------------------------------------------------------------------
  77. bool cmCTestGIT::UpdateImpl()
  78. {
  79. // Use "git pull" to update the working tree.
  80. std::vector<char const*> git_pull;
  81. git_pull.push_back(this->CommandLineTool.c_str());
  82. git_pull.push_back("pull");
  83. // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
  84. // Add user-specified update options.
  85. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  86. if(opts.empty())
  87. {
  88. opts = this->CTest->GetCTestConfiguration("GITUpdateOptions");
  89. }
  90. std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
  91. for(std::vector<cmStdString>::const_iterator ai = args.begin();
  92. ai != args.end(); ++ai)
  93. {
  94. git_pull.push_back(ai->c_str());
  95. }
  96. // Sentinel argument.
  97. git_pull.push_back(0);
  98. OutputLogger out(this->Log, "pull-out> ");
  99. OutputLogger err(this->Log, "pull-err> ");
  100. return this->RunUpdateCommand(&git_pull[0], &out, &err);
  101. }
  102. //----------------------------------------------------------------------------
  103. /* Diff format:
  104. :src-mode dst-mode src-sha1 dst-sha1 status\0
  105. src-path\0
  106. [dst-path\0]
  107. The format is repeated for every file changed. The [dst-path\0]
  108. line appears only for lines with status 'C' or 'R'. See 'git help
  109. diff-tree' for details.
  110. */
  111. class cmCTestGIT::DiffParser: public cmCTestVC::LineParser
  112. {
  113. public:
  114. DiffParser(cmCTestGIT* git, const char* prefix):
  115. LineParser('\0', false), GIT(git), DiffField(DiffFieldNone)
  116. {
  117. this->SetLog(&git->Log, prefix);
  118. }
  119. typedef cmCTestGIT::Change Change;
  120. std::vector<Change> Changes;
  121. protected:
  122. cmCTestGIT* GIT;
  123. enum DiffFieldType { DiffFieldNone, DiffFieldChange,
  124. DiffFieldSrc, DiffFieldDst };
  125. DiffFieldType DiffField;
  126. Change CurChange;
  127. void DiffReset()
  128. {
  129. this->DiffField = DiffFieldNone;
  130. this->Changes.clear();
  131. }
  132. virtual bool ProcessLine()
  133. {
  134. if(this->Line[0] == ':')
  135. {
  136. this->DiffField = DiffFieldChange;
  137. this->CurChange = Change();
  138. }
  139. if(this->DiffField == DiffFieldChange)
  140. {
  141. // :src-mode dst-mode src-sha1 dst-sha1 status
  142. if(this->Line[0] != ':')
  143. {
  144. this->DiffField = DiffFieldNone;
  145. return true;
  146. }
  147. const char* src_mode_first = this->Line.c_str()+1;
  148. const char* src_mode_last = this->ConsumeField(src_mode_first);
  149. const char* dst_mode_first = this->ConsumeSpace(src_mode_last);
  150. const char* dst_mode_last = this->ConsumeField(dst_mode_first);
  151. const char* src_sha1_first = this->ConsumeSpace(dst_mode_last);
  152. const char* src_sha1_last = this->ConsumeField(src_sha1_first);
  153. const char* dst_sha1_first = this->ConsumeSpace(src_sha1_last);
  154. const char* dst_sha1_last = this->ConsumeField(dst_sha1_first);
  155. const char* status_first = this->ConsumeSpace(dst_sha1_last);
  156. const char* status_last = this->ConsumeField(status_first);
  157. if(status_first != status_last)
  158. {
  159. this->CurChange.Action = *status_first;
  160. this->DiffField = DiffFieldSrc;
  161. }
  162. else
  163. {
  164. this->DiffField = DiffFieldNone;
  165. }
  166. }
  167. else if(this->DiffField == DiffFieldSrc)
  168. {
  169. // src-path
  170. if(this->CurChange.Action == 'C')
  171. {
  172. // Convert copy to addition of destination.
  173. this->CurChange.Action = 'A';
  174. this->DiffField = DiffFieldDst;
  175. }
  176. else if(this->CurChange.Action == 'R')
  177. {
  178. // Convert rename to deletion of source and addition of destination.
  179. this->CurChange.Action = 'D';
  180. this->CurChange.Path = this->Line;
  181. this->Changes.push_back(this->CurChange);
  182. this->CurChange = Change('A');
  183. this->DiffField = DiffFieldDst;
  184. }
  185. else
  186. {
  187. this->CurChange.Path = this->Line;
  188. this->Changes.push_back(this->CurChange);
  189. this->DiffField = this->DiffFieldNone;
  190. }
  191. }
  192. else if(this->DiffField == DiffFieldDst)
  193. {
  194. // dst-path
  195. this->CurChange.Path = this->Line;
  196. this->Changes.push_back(this->CurChange);
  197. this->DiffField = this->DiffFieldNone;
  198. }
  199. return true;
  200. }
  201. const char* ConsumeSpace(const char* c)
  202. {
  203. while(*c && isspace(*c)) { ++c; }
  204. return c;
  205. }
  206. const char* ConsumeField(const char* c)
  207. {
  208. while(*c && !isspace(*c)) { ++c; }
  209. return c;
  210. }
  211. };
  212. //----------------------------------------------------------------------------
  213. /* Commit format:
  214. commit ...\n
  215. tree ...\n
  216. parent ...\n
  217. author ...\n
  218. committer ...\n
  219. \n
  220. Log message indented by (4) spaces\n
  221. (even blank lines have the spaces)\n
  222. \n
  223. [Diff format]
  224. The header may have more fields. See 'git help diff-tree'.
  225. */
  226. class cmCTestGIT::CommitParser: public DiffParser
  227. {
  228. public:
  229. CommitParser(cmCTestGIT* git, const char* prefix):
  230. DiffParser(git, prefix), Section(SectionHeader)
  231. {
  232. this->Separator = SectionSep[this->Section];
  233. }
  234. private:
  235. typedef cmCTestGIT::Revision Revision;
  236. enum SectionType { SectionHeader, SectionBody, SectionDiff, SectionCount };
  237. static char const SectionSep[SectionCount];
  238. SectionType Section;
  239. Revision Rev;
  240. struct Person
  241. {
  242. std::string Name;
  243. std::string EMail;
  244. unsigned long Time;
  245. long TimeZone;
  246. Person(): Name(), EMail(), Time(0), TimeZone(0) {}
  247. };
  248. void ParsePerson(const char* str, Person& person)
  249. {
  250. // Person Name <[email protected]> 1234567890 +0000
  251. const char* c = str;
  252. while(*c && isspace(*c)) { ++c; }
  253. const char* name_first = c;
  254. while(*c && *c != '<') { ++c; }
  255. const char* name_last = c;
  256. while(name_last != name_first && isspace(*(name_last-1))) { --name_last; }
  257. person.Name.assign(name_first, name_last-name_first);
  258. const char* email_first = *c? ++c : c;
  259. while(*c && *c != '>') { ++c; }
  260. const char* email_last = *c? c++ : c;
  261. person.EMail.assign(email_first, email_last-email_first);
  262. person.Time = strtoul(c, (char**)&c, 10);
  263. person.TimeZone = strtol(c, (char**)&c, 10);
  264. }
  265. virtual bool ProcessLine()
  266. {
  267. if(this->Line.empty())
  268. {
  269. this->NextSection();
  270. }
  271. else
  272. {
  273. switch(this->Section)
  274. {
  275. case SectionHeader: this->DoHeaderLine(); break;
  276. case SectionBody: this->DoBodyLine(); break;
  277. case SectionDiff: this->DiffParser::ProcessLine(); break;
  278. case SectionCount: break; // never happens
  279. }
  280. }
  281. return true;
  282. }
  283. void NextSection()
  284. {
  285. this->Section = SectionType((this->Section+1) % SectionCount);
  286. this->Separator = SectionSep[this->Section];
  287. if(this->Section == SectionHeader)
  288. {
  289. this->GIT->DoRevision(this->Rev, this->Changes);
  290. this->Rev = Revision();
  291. this->DiffReset();
  292. }
  293. }
  294. void DoHeaderLine()
  295. {
  296. // Look for header fields that we need.
  297. if(strncmp(this->Line.c_str(), "commit ", 7) == 0)
  298. {
  299. this->Rev.Rev = this->Line.c_str()+7;
  300. }
  301. else if(strncmp(this->Line.c_str(), "author ", 7) == 0)
  302. {
  303. Person author;
  304. this->ParsePerson(this->Line.c_str()+7, author);
  305. this->Rev.Author = author.Name;
  306. char buf[1024];
  307. if(author.TimeZone >= 0)
  308. {
  309. sprintf(buf, "%lu +%04ld", author.Time, author.TimeZone);
  310. }
  311. else
  312. {
  313. sprintf(buf, "%lu -%04ld", author.Time, -author.TimeZone);
  314. }
  315. this->Rev.Date = buf;
  316. }
  317. }
  318. void DoBodyLine()
  319. {
  320. // Commit log lines are indented by 4 spaces.
  321. if(this->Line.size() >= 4)
  322. {
  323. this->Rev.Log += this->Line.substr(4);
  324. }
  325. this->Rev.Log += "\n";
  326. }
  327. };
  328. char const cmCTestGIT::CommitParser::SectionSep[SectionCount] =
  329. {'\n', '\n', '\0'};
  330. //----------------------------------------------------------------------------
  331. void cmCTestGIT::LoadRevisions()
  332. {
  333. // Use 'git rev-list ... | git diff-tree ...' to get revisions.
  334. std::string range = this->OldRevision + ".." + this->NewRevision;
  335. const char* git = this->CommandLineTool.c_str();
  336. const char* git_rev_list[] =
  337. {git, "rev-list", "--reverse", range.c_str(), "--", 0};
  338. const char* git_diff_tree[] =
  339. {git, "diff-tree", "--stdin", "--always", "-z", "-r", "--pretty=raw",
  340. "--encoding=utf-8", 0};
  341. this->Log << this->ComputeCommandLine(git_rev_list) << " | "
  342. << this->ComputeCommandLine(git_diff_tree) << "\n";
  343. cmsysProcess* cp = cmsysProcess_New();
  344. cmsysProcess_AddCommand(cp, git_rev_list);
  345. cmsysProcess_AddCommand(cp, git_diff_tree);
  346. cmsysProcess_SetWorkingDirectory(cp, this->SourceDirectory.c_str());
  347. CommitParser out(this, "dt-out> ");
  348. OutputLogger err(this->Log, "dt-err> ");
  349. this->RunProcess(cp, &out, &err);
  350. // Send one extra zero-byte to terminate the last record.
  351. out.Process("", 1);
  352. cmsysProcess_Delete(cp);
  353. }
  354. //----------------------------------------------------------------------------
  355. void cmCTestGIT::LoadModifications()
  356. {
  357. // Use 'git diff-index' to get modified files.
  358. const char* git = this->CommandLineTool.c_str();
  359. const char* git_diff_index[] = {git, "diff-index", "-z", "HEAD", 0};
  360. DiffParser out(this, "di-out> ");
  361. OutputLogger err(this->Log, "di-err> ");
  362. this->RunChild(git_diff_index, &out, &err);
  363. for(std::vector<Change>::const_iterator ci = out.Changes.begin();
  364. ci != out.Changes.end(); ++ci)
  365. {
  366. this->DoModification(PathModified, ci->Path);
  367. }
  368. }