cmCTestGIT.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCTestGIT.h"
  4. #include <cstdio>
  5. #include <cstdlib>
  6. #include <ctime>
  7. #include <utility>
  8. #include <vector>
  9. #include <cmext/algorithm>
  10. #include "cmsys/FStream.hxx"
  11. #include "cmCTest.h"
  12. #include "cmCTestVC.h"
  13. #include "cmList.h"
  14. #include "cmMakefile.h"
  15. #include "cmProcessOutput.h"
  16. #include "cmStringAlgorithms.h"
  17. #include "cmSystemTools.h"
  18. #include "cmUVProcessChain.h"
  19. static unsigned int cmCTestGITVersion(unsigned int epic, unsigned int major,
  20. unsigned int minor, unsigned int fix)
  21. {
  22. // 1.6.5.0 maps to 10605000
  23. return epic * 10000000 + major * 100000 + minor * 1000 + fix;
  24. }
  25. cmCTestGIT::cmCTestGIT(cmCTest* ct, cmMakefile* mf, std::ostream& log)
  26. : cmCTestGlobalVC(ct, mf, log)
  27. {
  28. this->PriorRev = this->Unknown;
  29. this->CurrentGitVersion = 0;
  30. }
  31. cmCTestGIT::~cmCTestGIT() = default;
  32. class cmCTestGIT::OneLineParser : public cmCTestVC::LineParser
  33. {
  34. public:
  35. OneLineParser(cmCTestGIT* git, const char* prefix, std::string& l)
  36. : Line1(l)
  37. {
  38. this->SetLog(&git->Log, prefix);
  39. }
  40. private:
  41. std::string& Line1;
  42. bool ProcessLine() override
  43. {
  44. // Only the first line is of interest.
  45. this->Line1 = this->Line;
  46. return false;
  47. }
  48. };
  49. std::string cmCTestGIT::GetWorkingRevision()
  50. {
  51. // Run plumbing "git rev-list" to get work tree revision.
  52. std::string git = this->CommandLineTool;
  53. std::vector<std::string> git_rev_list = { git, "rev-list", "-n",
  54. "1", "HEAD", "--" };
  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. bool cmCTestGIT::NoteOldRevision()
  62. {
  63. this->OldRevision = this->GetWorkingRevision();
  64. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  65. " Old revision of repository is: " << this->OldRevision
  66. << "\n");
  67. this->PriorRev.Rev = this->OldRevision;
  68. return true;
  69. }
  70. bool cmCTestGIT::NoteNewRevision()
  71. {
  72. this->NewRevision = this->GetWorkingRevision();
  73. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  74. " New revision of repository is: " << this->NewRevision
  75. << "\n");
  76. return true;
  77. }
  78. std::string cmCTestGIT::FindGitDir()
  79. {
  80. std::string git_dir;
  81. // Run "git rev-parse --git-dir" to locate the real .git directory.
  82. std::string git = this->CommandLineTool;
  83. std::vector<std::string> git_rev_parse = { git, "rev-parse", "--git-dir" };
  84. std::string git_dir_line;
  85. OneLineParser rev_parse_out(this, "rev-parse-out> ", git_dir_line);
  86. OutputLogger rev_parse_err(this->Log, "rev-parse-err> ");
  87. if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err,
  88. std::string{}, cmProcessOutput::UTF8)) {
  89. git_dir = git_dir_line;
  90. }
  91. if (git_dir.empty()) {
  92. git_dir = ".git";
  93. }
  94. // Git reports a relative path only when the .git directory is in
  95. // the current directory.
  96. if (git_dir[0] == '.') {
  97. git_dir = this->SourceDirectory + "/" + git_dir;
  98. }
  99. #if defined(_WIN32) && !defined(__CYGWIN__)
  100. else if (git_dir[0] == '/') {
  101. // Cygwin Git reports a full path that Cygwin understands, but we
  102. // are a Windows application. Run "cygpath" to get Windows path.
  103. std::string cygpath_exe =
  104. cmStrCat(cmSystemTools::GetFilenamePath(git), "/cygpath.exe");
  105. if (cmSystemTools::FileExists(cygpath_exe)) {
  106. std::vector<std::string> cygpath = { cygpath_exe, "-w", git_dir };
  107. OneLineParser cygpath_out(this, "cygpath-out> ", git_dir_line);
  108. OutputLogger cygpath_err(this->Log, "cygpath-err> ");
  109. if (this->RunChild(cygpath, &cygpath_out, &cygpath_err, std::string{},
  110. cmProcessOutput::UTF8)) {
  111. git_dir = git_dir_line;
  112. }
  113. }
  114. }
  115. #endif
  116. return git_dir;
  117. }
  118. std::string cmCTestGIT::FindTopDir()
  119. {
  120. std::string top_dir = this->SourceDirectory;
  121. // Run "git rev-parse --show-cdup" to locate the top of the tree.
  122. std::string git = this->CommandLineTool;
  123. std::vector<std::string> git_rev_parse = { git, "rev-parse", "--show-cdup" };
  124. std::string cdup;
  125. OneLineParser rev_parse_out(this, "rev-parse-out> ", cdup);
  126. OutputLogger rev_parse_err(this->Log, "rev-parse-err> ");
  127. if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, "",
  128. cmProcessOutput::UTF8) &&
  129. !cdup.empty()) {
  130. top_dir += "/";
  131. top_dir += cdup;
  132. top_dir = cmSystemTools::ToNormalizedPathOnDisk(top_dir);
  133. }
  134. return top_dir;
  135. }
  136. bool cmCTestGIT::UpdateByFetchAndReset()
  137. {
  138. std::string git = this->CommandLineTool;
  139. // Use "git fetch" to get remote commits.
  140. std::vector<std::string> git_fetch;
  141. git_fetch.push_back(git);
  142. git_fetch.emplace_back("fetch");
  143. // Add user-specified update options.
  144. std::string opts = this->Makefile->GetSafeDefinition("CTEST_UPDATE_OPTIONS");
  145. if (opts.empty()) {
  146. opts = this->Makefile->GetSafeDefinition("CTEST_GIT_UPDATE_OPTIONS");
  147. }
  148. std::vector<std::string> args = cmSystemTools::ParseArguments(opts);
  149. cm::append(git_fetch, args);
  150. // Fetch upstream refs.
  151. OutputLogger fetch_out(this->Log, "fetch-out> ");
  152. OutputLogger fetch_err(this->Log, "fetch-err> ");
  153. if (!this->RunUpdateCommand(git_fetch, &fetch_out, &fetch_err)) {
  154. return false;
  155. }
  156. // Identify the merge head that would be used by "git pull".
  157. std::string sha1;
  158. {
  159. std::string fetch_head = this->FindGitDir() + "/FETCH_HEAD";
  160. cmsys::ifstream fin(fetch_head.c_str(), std::ios::in | std::ios::binary);
  161. if (!fin) {
  162. this->Log << "Unable to open " << fetch_head << "\n";
  163. return false;
  164. }
  165. std::string line;
  166. while (sha1.empty() && cmSystemTools::GetLineFromStream(fin, line)) {
  167. this->Log << "FETCH_HEAD> " << line << "\n";
  168. if (line.find("\tnot-for-merge\t") == std::string::npos) {
  169. std::string::size_type pos = line.find('\t');
  170. if (pos != std::string::npos) {
  171. sha1 = std::move(line);
  172. sha1.resize(pos);
  173. }
  174. }
  175. }
  176. if (sha1.empty()) {
  177. this->Log << "FETCH_HEAD has no upstream branch candidate!\n";
  178. return false;
  179. }
  180. }
  181. // Reset the local branch to point at that tracked from upstream.
  182. std::vector<std::string> git_reset = { git, "reset", "--hard", sha1 };
  183. OutputLogger reset_out(this->Log, "reset-out> ");
  184. OutputLogger reset_err(this->Log, "reset-err> ");
  185. return this->RunChild(git_reset, &reset_out, &reset_err);
  186. }
  187. bool cmCTestGIT::UpdateByCustom(std::string const& custom)
  188. {
  189. cmList git_custom_command{ custom, cmList::EmptyElements::Yes };
  190. std::vector<std::string> git_custom;
  191. git_custom.reserve(git_custom_command.size());
  192. cm::append(git_custom, git_custom_command);
  193. OutputLogger custom_out(this->Log, "custom-out> ");
  194. OutputLogger custom_err(this->Log, "custom-err> ");
  195. return this->RunUpdateCommand(git_custom, &custom_out, &custom_err);
  196. }
  197. bool cmCTestGIT::UpdateInternal()
  198. {
  199. std::string custom =
  200. this->Makefile->GetSafeDefinition("CTEST_GIT_UPDATE_CUSTOM");
  201. if (!custom.empty()) {
  202. return this->UpdateByCustom(custom);
  203. }
  204. return this->UpdateByFetchAndReset();
  205. }
  206. bool cmCTestGIT::UpdateImpl()
  207. {
  208. if (!this->UpdateInternal()) {
  209. return false;
  210. }
  211. std::string top_dir = this->FindTopDir();
  212. std::string git = this->CommandLineTool;
  213. std::string recursive = "--recursive";
  214. std::string sync_recursive = "--recursive";
  215. // Git < 1.6.5 did not support submodule --recursive
  216. bool support_recursive = true;
  217. if (this->GetGitVersion() < cmCTestGITVersion(1, 6, 5, 0)) {
  218. support_recursive = false;
  219. // No need to require >= 1.6.5 if there are no submodules.
  220. if (cmSystemTools::FileExists(top_dir + "/.gitmodules")) {
  221. this->Log << "Git < 1.6.5 cannot update submodules recursively\n";
  222. }
  223. }
  224. // Git < 1.8.1 did not support sync --recursive
  225. bool support_sync_recursive = true;
  226. if (this->GetGitVersion() < cmCTestGITVersion(1, 8, 1, 0)) {
  227. support_sync_recursive = false;
  228. // No need to require >= 1.8.1 if there are no submodules.
  229. if (cmSystemTools::FileExists(top_dir + "/.gitmodules")) {
  230. this->Log << "Git < 1.8.1 cannot synchronize submodules recursively\n";
  231. }
  232. }
  233. OutputLogger submodule_out(this->Log, "submodule-out> ");
  234. OutputLogger submodule_err(this->Log, "submodule-err> ");
  235. bool ret;
  236. if (this->Makefile->IsOn("CTEST_GIT_INIT_SUBMODULES")) {
  237. std::vector<std::string> git_submodule_init = { git, "submodule", "init" };
  238. ret = this->RunChild(git_submodule_init, &submodule_out, &submodule_err,
  239. top_dir);
  240. if (!ret) {
  241. return false;
  242. }
  243. }
  244. std::vector<std::string> git_submodule_sync = { git, "submodule", "sync" };
  245. if (support_sync_recursive) {
  246. git_submodule_sync.push_back(sync_recursive);
  247. }
  248. ret = this->RunChild(git_submodule_sync, &submodule_out, &submodule_err,
  249. top_dir);
  250. if (!ret) {
  251. return false;
  252. }
  253. std::vector<std::string> git_submodule = { git, "submodule", "update" };
  254. if (support_recursive) {
  255. git_submodule.push_back(recursive);
  256. }
  257. return this->RunChild(git_submodule, &submodule_out, &submodule_err,
  258. top_dir);
  259. }
  260. unsigned int cmCTestGIT::GetGitVersion()
  261. {
  262. if (!this->CurrentGitVersion) {
  263. std::string git = this->CommandLineTool;
  264. std::vector<std::string> git_version = { git, "--version" };
  265. std::string version;
  266. OneLineParser version_out(this, "version-out> ", version);
  267. OutputLogger version_err(this->Log, "version-err> ");
  268. unsigned int v[4] = { 0, 0, 0, 0 };
  269. if (this->RunChild(git_version, &version_out, &version_err) &&
  270. sscanf(version.c_str(), "git version %u.%u.%u.%u", &v[0], &v[1], &v[2],
  271. &v[3]) >= 3) {
  272. this->CurrentGitVersion = cmCTestGITVersion(v[0], v[1], v[2], v[3]);
  273. }
  274. }
  275. return this->CurrentGitVersion;
  276. }
  277. /* Diff format:
  278. :src-mode dst-mode src-sha1 dst-sha1 status\0
  279. src-path\0
  280. [dst-path\0]
  281. The format is repeated for every file changed. The [dst-path\0]
  282. line appears only for lines with status 'C' or 'R'. See 'git help
  283. diff-tree' for details.
  284. */
  285. class cmCTestGIT::DiffParser : public cmCTestVC::LineParser
  286. {
  287. public:
  288. DiffParser(cmCTestGIT* git, const char* prefix)
  289. : LineParser('\0', false)
  290. , GIT(git)
  291. {
  292. this->SetLog(&git->Log, prefix);
  293. }
  294. using Change = cmCTestGIT::Change;
  295. std::vector<Change> Changes;
  296. protected:
  297. cmCTestGIT* GIT;
  298. enum DiffFieldType
  299. {
  300. DiffFieldNone,
  301. DiffFieldChange,
  302. DiffFieldSrc,
  303. DiffFieldDst
  304. };
  305. DiffFieldType DiffField = DiffFieldNone;
  306. Change CurChange;
  307. void DiffReset()
  308. {
  309. this->DiffField = DiffFieldNone;
  310. this->Changes.clear();
  311. }
  312. bool ProcessLine() override
  313. {
  314. if (this->Line[0] == ':') {
  315. this->DiffField = DiffFieldChange;
  316. this->CurChange = Change();
  317. }
  318. if (this->DiffField == DiffFieldChange) {
  319. // :src-mode dst-mode src-sha1 dst-sha1 status
  320. if (this->Line[0] != ':') {
  321. this->DiffField = DiffFieldNone;
  322. return true;
  323. }
  324. const char* src_mode_first = this->Line.c_str() + 1;
  325. const char* src_mode_last = this->ConsumeField(src_mode_first);
  326. const char* dst_mode_first = this->ConsumeSpace(src_mode_last);
  327. const char* dst_mode_last = this->ConsumeField(dst_mode_first);
  328. const char* src_sha1_first = this->ConsumeSpace(dst_mode_last);
  329. const char* src_sha1_last = this->ConsumeField(src_sha1_first);
  330. const char* dst_sha1_first = this->ConsumeSpace(src_sha1_last);
  331. const char* dst_sha1_last = this->ConsumeField(dst_sha1_first);
  332. const char* status_first = this->ConsumeSpace(dst_sha1_last);
  333. const char* status_last = this->ConsumeField(status_first);
  334. if (status_first != status_last) {
  335. this->CurChange.Action = *status_first;
  336. this->DiffField = DiffFieldSrc;
  337. } else {
  338. this->DiffField = DiffFieldNone;
  339. }
  340. } else if (this->DiffField == DiffFieldSrc) {
  341. // src-path
  342. if (this->CurChange.Action == 'C') {
  343. // Convert copy to addition of destination.
  344. this->CurChange.Action = 'A';
  345. this->DiffField = DiffFieldDst;
  346. } else if (this->CurChange.Action == 'R') {
  347. // Convert rename to deletion of source and addition of destination.
  348. this->CurChange.Action = 'D';
  349. this->CurChange.Path = this->Line;
  350. this->Changes.push_back(this->CurChange);
  351. this->CurChange = Change('A');
  352. this->DiffField = DiffFieldDst;
  353. } else {
  354. this->CurChange.Path = this->Line;
  355. this->Changes.push_back(this->CurChange);
  356. this->DiffField = this->DiffFieldNone;
  357. }
  358. } else if (this->DiffField == DiffFieldDst) {
  359. // dst-path
  360. this->CurChange.Path = this->Line;
  361. this->Changes.push_back(this->CurChange);
  362. this->DiffField = this->DiffFieldNone;
  363. }
  364. return true;
  365. }
  366. const char* ConsumeSpace(const char* c)
  367. {
  368. while (*c && cmIsSpace(*c)) {
  369. ++c;
  370. }
  371. return c;
  372. }
  373. const char* ConsumeField(const char* c)
  374. {
  375. while (*c && !cmIsSpace(*c)) {
  376. ++c;
  377. }
  378. return c;
  379. }
  380. };
  381. /* Commit format:
  382. commit ...\n
  383. tree ...\n
  384. parent ...\n
  385. author ...\n
  386. committer ...\n
  387. \n
  388. Log message indented by (4) spaces\n
  389. (even blank lines have the spaces)\n
  390. [[
  391. \n
  392. [Diff format]
  393. OR
  394. \0
  395. ]]
  396. The header may have more fields. See 'git help diff-tree'.
  397. */
  398. class cmCTestGIT::CommitParser : public cmCTestGIT::DiffParser
  399. {
  400. public:
  401. CommitParser(cmCTestGIT* git, const char* prefix)
  402. : DiffParser(git, prefix)
  403. {
  404. this->Separator = SectionSep[this->Section];
  405. }
  406. private:
  407. using Revision = cmCTestGIT::Revision;
  408. enum SectionType
  409. {
  410. SectionHeader,
  411. SectionBody,
  412. SectionDiff,
  413. SectionCount
  414. };
  415. static char const SectionSep[SectionCount];
  416. SectionType Section = SectionHeader;
  417. Revision Rev;
  418. struct Person
  419. {
  420. std::string Name;
  421. std::string EMail;
  422. unsigned long Time = 0;
  423. long TimeZone = 0;
  424. };
  425. void ParsePerson(const char* str, Person& person)
  426. {
  427. // Person Name <[email protected]> 1234567890 +0000
  428. const char* c = str;
  429. while (*c && cmIsSpace(*c)) {
  430. ++c;
  431. }
  432. const char* name_first = c;
  433. while (*c && *c != '<') {
  434. ++c;
  435. }
  436. const char* name_last = c;
  437. while (name_last != name_first && cmIsSpace(*(name_last - 1))) {
  438. --name_last;
  439. }
  440. person.Name.assign(name_first, name_last - name_first);
  441. const char* email_first = *c ? ++c : c;
  442. while (*c && *c != '>') {
  443. ++c;
  444. }
  445. const char* email_last = *c ? c++ : c;
  446. person.EMail.assign(email_first, email_last - email_first);
  447. person.Time = strtoul(c, const_cast<char**>(&c), 10);
  448. person.TimeZone = strtol(c, const_cast<char**>(&c), 10);
  449. }
  450. bool ProcessLine() override
  451. {
  452. if (this->Line.empty()) {
  453. if (this->Section == SectionBody && this->LineEnd == '\0') {
  454. // Skip SectionDiff
  455. this->NextSection();
  456. }
  457. this->NextSection();
  458. } else {
  459. switch (this->Section) {
  460. case SectionHeader:
  461. this->DoHeaderLine();
  462. break;
  463. case SectionBody:
  464. this->DoBodyLine();
  465. break;
  466. case SectionDiff:
  467. this->DiffParser::ProcessLine();
  468. break;
  469. case SectionCount:
  470. break; // never happens
  471. }
  472. }
  473. return true;
  474. }
  475. void NextSection()
  476. {
  477. this->Section =
  478. static_cast<SectionType>((this->Section + 1) % SectionCount);
  479. this->Separator = SectionSep[this->Section];
  480. if (this->Section == SectionHeader) {
  481. this->GIT->DoRevision(this->Rev, this->Changes);
  482. this->Rev = Revision();
  483. this->DiffReset();
  484. }
  485. }
  486. void DoHeaderLine()
  487. {
  488. // Look for header fields that we need.
  489. if (cmHasLiteralPrefix(this->Line, "commit ")) {
  490. this->Rev.Rev = this->Line.substr(7);
  491. } else if (cmHasLiteralPrefix(this->Line, "author ")) {
  492. Person author;
  493. this->ParsePerson(this->Line.c_str() + 7, author);
  494. this->Rev.Author = author.Name;
  495. this->Rev.EMail = author.EMail;
  496. this->Rev.Date = this->FormatDateTime(author);
  497. } else if (cmHasLiteralPrefix(this->Line, "committer ")) {
  498. Person committer;
  499. this->ParsePerson(this->Line.c_str() + 10, committer);
  500. this->Rev.Committer = committer.Name;
  501. this->Rev.CommitterEMail = committer.EMail;
  502. this->Rev.CommitDate = this->FormatDateTime(committer);
  503. }
  504. }
  505. void DoBodyLine()
  506. {
  507. // Commit log lines are indented by 4 spaces.
  508. if (this->Line.size() >= 4) {
  509. this->Rev.Log += this->Line.substr(4);
  510. }
  511. this->Rev.Log += "\n";
  512. }
  513. std::string FormatDateTime(Person const& person)
  514. {
  515. // Convert the time to a human-readable format that is also easy
  516. // to machine-parse: "CCYY-MM-DD hh:mm:ss".
  517. time_t seconds = static_cast<time_t>(person.Time);
  518. struct tm* t = gmtime(&seconds);
  519. char dt[1024];
  520. snprintf(dt, sizeof(dt), "%04d-%02d-%02d %02d:%02d:%02d",
  521. t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour,
  522. t->tm_min, t->tm_sec);
  523. std::string out = dt;
  524. // Add the time-zone field "+zone" or "-zone".
  525. char tz[32];
  526. if (person.TimeZone >= 0) {
  527. snprintf(tz, sizeof(tz), " +%04ld", person.TimeZone);
  528. } else {
  529. snprintf(tz, sizeof(tz), " -%04ld", -person.TimeZone);
  530. }
  531. out += tz;
  532. return out;
  533. }
  534. };
  535. char const cmCTestGIT::CommitParser::SectionSep[SectionCount] = { '\n', '\n',
  536. '\0' };
  537. bool cmCTestGIT::LoadRevisions()
  538. {
  539. // Use 'git rev-list ... | git diff-tree ...' to get revisions.
  540. std::string range = this->OldRevision + ".." + this->NewRevision;
  541. std::string git = this->CommandLineTool;
  542. std::vector<std::string> git_rev_list = { git, "rev-list", "--reverse",
  543. range, "--" };
  544. std::vector<std::string> git_diff_tree = {
  545. git, "diff-tree", "--stdin", "--always",
  546. "-z", "-r", "--pretty=raw", "--encoding=utf-8"
  547. };
  548. this->Log << cmCTestGIT::ComputeCommandLine(git_rev_list) << " | "
  549. << cmCTestGIT::ComputeCommandLine(git_diff_tree) << "\n";
  550. cmUVProcessChainBuilder builder;
  551. builder.AddCommand(git_rev_list)
  552. .AddCommand(git_diff_tree)
  553. .SetWorkingDirectory(this->SourceDirectory);
  554. CommitParser out(this, "dt-out> ");
  555. OutputLogger err(this->Log, "dt-err> ");
  556. cmCTestGIT::RunProcess(builder, &out, &err, cmProcessOutput::UTF8);
  557. // Send one extra zero-byte to terminate the last record.
  558. out.Process("", 1);
  559. return true;
  560. }
  561. bool cmCTestGIT::LoadModifications()
  562. {
  563. std::string git = this->CommandLineTool;
  564. // Use 'git update-index' to refresh the index w.r.t. the work tree.
  565. std::vector<std::string> git_update_index = { git, "update-index",
  566. "--refresh" };
  567. OutputLogger ui_out(this->Log, "ui-out> ");
  568. OutputLogger ui_err(this->Log, "ui-err> ");
  569. this->RunChild(git_update_index, &ui_out, &ui_err, "",
  570. cmProcessOutput::UTF8);
  571. // Use 'git diff-index' to get modified files.
  572. std::vector<std::string> git_diff_index = { git, "diff-index", "-z", "HEAD",
  573. "--" };
  574. DiffParser out(this, "di-out> ");
  575. OutputLogger err(this->Log, "di-err> ");
  576. this->RunChild(git_diff_index, &out, &err, "", cmProcessOutput::UTF8);
  577. for (Change const& c : out.Changes) {
  578. this->DoModification(PathModified, c.Path);
  579. }
  580. return true;
  581. }