cmCTestGIT.cxx 19.4 KB
Newer Older
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
4
#include "cmCTestGIT.h"

5
6
#include "cmsys/FStream.hxx"
#include "cmsys/Process.h"
7
#include <ctype.h>
8
9
#include <stdio.h>
#include <stdlib.h>
10
#include <time.h>
11
#include <vector>
12

Daniel Pfeifer's avatar
Daniel Pfeifer committed
13
14
15
16
17
18
19
#include "cmAlgorithms.h"
#include "cmCTest.h"
#include "cmCTestVC.h"
#include "cmProcessOutput.h"
#include "cmProcessTools.h"
#include "cmSystemTools.h"

20
21
22
23
static unsigned int cmCTestGITVersion(unsigned int epic, unsigned int major,
                                      unsigned int minor, unsigned int fix)
{
  // 1.6.5.0 maps to 10605000
24
  return fix + minor * 1000 + major * 100000 + epic * 10000000;
25
26
}

27
28
cmCTestGIT::cmCTestGIT(cmCTest* ct, std::ostream& log)
  : cmCTestGlobalVC(ct, log)
29
30
{
  this->PriorRev = this->Unknown;
31
  this->CurrentGitVersion = 0;
32
33
34
35
36
37
}

cmCTestGIT::~cmCTestGIT()
{
}

38
class cmCTestGIT::OneLineParser : public cmCTestVC::LineParser
39
40
{
public:
41
42
43
  OneLineParser(cmCTestGIT* git, const char* prefix, std::string& l)
    : Line1(l)
  {
44
    this->SetLog(&git->Log, prefix);
45
46
  }

47
48
private:
  std::string& Line1;
49
  bool ProcessLine() override
50
  {
51
52
53
    // Only the first line is of interest.
    this->Line1 = this->Line;
    return false;
54
  }
55
56
57
58
59
60
};

std::string cmCTestGIT::GetWorkingRevision()
{
  // Run plumbing "git rev-list" to get work tree revision.
  const char* git = this->CommandLineTool.c_str();
Daniel Pfeifer's avatar
Daniel Pfeifer committed
61
62
  const char* git_rev_list[] = { git,    "rev-list", "-n",   "1",
                                 "HEAD", "--",       nullptr };
63
64
65
66
67
68
69
  std::string rev;
  OneLineParser out(this, "rl-out> ", rev);
  OutputLogger err(this->Log, "rl-err> ");
  this->RunChild(git_rev_list, &out, &err);
  return rev;
}

70
bool cmCTestGIT::NoteOldRevision()
71
72
73
{
  this->OldRevision = this->GetWorkingRevision();
  cmCTestLog(this->CTest, HANDLER_OUTPUT, "   Old revision of repository is: "
74
               << this->OldRevision << "\n");
75
  this->PriorRev.Rev = this->OldRevision;
76
  return true;
77
78
}

79
bool cmCTestGIT::NoteNewRevision()
80
81
82
{
  this->NewRevision = this->GetWorkingRevision();
  cmCTestLog(this->CTest, HANDLER_OUTPUT, "   New revision of repository is: "
83
               << this->NewRevision << "\n");
84
  return true;
85
86
}

87
88
89
90
91
92
std::string cmCTestGIT::FindGitDir()
{
  std::string git_dir;

  // Run "git rev-parse --git-dir" to locate the real .git directory.
  const char* git = this->CommandLineTool.c_str();
Daniel Pfeifer's avatar
Daniel Pfeifer committed
93
  char const* git_rev_parse[] = { git, "rev-parse", "--git-dir", nullptr };
94
95
96
  std::string git_dir_line;
  OneLineParser rev_parse_out(this, "rev-parse-out> ", git_dir_line);
  OutputLogger rev_parse_err(this->Log, "rev-parse-err> ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
97
  if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, nullptr,
98
                     cmProcessOutput::UTF8)) {
99
    git_dir = git_dir_line;
100
101
  }
  if (git_dir.empty()) {
102
    git_dir = ".git";
103
  }
104
105
106

  // Git reports a relative path only when the .git directory is in
  // the current directory.
107
  if (git_dir[0] == '.') {
108
    git_dir = this->SourceDirectory + "/" + git_dir;
109
  }
110
#if defined(_WIN32) && !defined(__CYGWIN__)
111
  else if (git_dir[0] == '/') {
112
113
114
115
    // Cygwin Git reports a full path that Cygwin understands, but we
    // are a Windows application.  Run "cygpath" to get Windows path.
    std::string cygpath_exe = cmSystemTools::GetFilenamePath(git);
    cygpath_exe += "/cygpath.exe";
116
    if (cmSystemTools::FileExists(cygpath_exe)) {
117
118
      char const* cygpath[] = { cygpath_exe.c_str(), "-w", git_dir.c_str(),
                                0 };
119
120
      OneLineParser cygpath_out(this, "cygpath-out> ", git_dir_line);
      OutputLogger cygpath_err(this->Log, "cygpath-err> ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
121
      if (this->RunChild(cygpath, &cygpath_out, &cygpath_err, nullptr,
122
                         cmProcessOutput::UTF8)) {
123
124
125
        git_dir = git_dir_line;
      }
    }
126
  }
127
128
129
130
#endif
  return git_dir;
}

131
132
133
134
135
136
std::string cmCTestGIT::FindTopDir()
{
  std::string top_dir = this->SourceDirectory;

  // Run "git rev-parse --show-cdup" to locate the top of the tree.
  const char* git = this->CommandLineTool.c_str();
Daniel Pfeifer's avatar
Daniel Pfeifer committed
137
  char const* git_rev_parse[] = { git, "rev-parse", "--show-cdup", nullptr };
138
139
140
  std::string cdup;
  OneLineParser rev_parse_out(this, "rev-parse-out> ", cdup);
  OutputLogger rev_parse_err(this->Log, "rev-parse-err> ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
141
  if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, nullptr,
142
                     cmProcessOutput::UTF8) &&
143
      !cdup.empty()) {
144
145
    top_dir += "/";
    top_dir += cdup;
146
    top_dir = cmSystemTools::CollapseFullPath(top_dir);
147
  }
148
149
150
  return top_dir;
}

151
bool cmCTestGIT::UpdateByFetchAndReset()
152
{
153
154
  const char* git = this->CommandLineTool.c_str();

155
156
157
158
  // Use "git fetch" to get remote commits.
  std::vector<char const*> git_fetch;
  git_fetch.push_back(git);
  git_fetch.push_back("fetch");
159
160
161

  // Add user-specified update options.
  std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
162
  if (opts.empty()) {
163
    opts = this->CTest->GetCTestConfiguration("GITUpdateOptions");
164
  }
165
  std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str());
166
167
  for (std::string const& arg : args) {
    git_fetch.push_back(arg.c_str());
168
  }
169
170

  // Sentinel argument.
Daniel Pfeifer's avatar
Daniel Pfeifer committed
171
  git_fetch.push_back(nullptr);
172
173
174
175

  // Fetch upstream refs.
  OutputLogger fetch_out(this->Log, "fetch-out> ");
  OutputLogger fetch_err(this->Log, "fetch-err> ");
176
  if (!this->RunUpdateCommand(&git_fetch[0], &fetch_out, &fetch_err)) {
177
    return false;
178
  }
179
180
181
182

  // Identify the merge head that would be used by "git pull".
  std::string sha1;
  {
183
184
185
186
187
    std::string fetch_head = this->FindGitDir() + "/FETCH_HEAD";
    cmsys::ifstream fin(fetch_head.c_str(), std::ios::in | std::ios::binary);
    if (!fin) {
      this->Log << "Unable to open " << fetch_head << "\n";
      return false;
188
    }
189
190
191
    std::string line;
    while (sha1.empty() && cmSystemTools::GetLineFromStream(fin, line)) {
      this->Log << "FETCH_HEAD> " << line << "\n";
192
      if (line.find("\tnot-for-merge\t") == std::string::npos) {
193
        std::string::size_type pos = line.find('\t');
194
        if (pos != std::string::npos) {
195
          sha1 = line.substr(0, pos);
196
197
198
        }
      }
    }
199
200
201
    if (sha1.empty()) {
      this->Log << "FETCH_HEAD has no upstream branch candidate!\n";
      return false;
202
    }
203
  }
204

205
  // Reset the local branch to point at that tracked from upstream.
Daniel Pfeifer's avatar
Daniel Pfeifer committed
206
  char const* git_reset[] = { git, "reset", "--hard", sha1.c_str(), nullptr };
207
208
209
  OutputLogger reset_out(this->Log, "reset-out> ");
  OutputLogger reset_err(this->Log, "reset-err> ");
  return this->RunChild(&git_reset[0], &reset_out, &reset_err);
210
211
212
213
214
215
216
}

bool cmCTestGIT::UpdateByCustom(std::string const& custom)
{
  std::vector<std::string> git_custom_command;
  cmSystemTools::ExpandListArgument(custom, git_custom_command, true);
  std::vector<char const*> git_custom;
217
  git_custom.reserve(git_custom_command.size() + 1);
218
219
  for (std::string const& i : git_custom_command) {
    git_custom.push_back(i.c_str());
220
  }
Daniel Pfeifer's avatar
Daniel Pfeifer committed
221
  git_custom.push_back(nullptr);
222
223
224
225
226
227
228
229
230

  OutputLogger custom_out(this->Log, "custom-out> ");
  OutputLogger custom_err(this->Log, "custom-err> ");
  return this->RunUpdateCommand(&git_custom[0], &custom_out, &custom_err);
}

bool cmCTestGIT::UpdateInternal()
{
  std::string custom = this->CTest->GetCTestConfiguration("GITUpdateCustom");
231
  if (!custom.empty()) {
232
    return this->UpdateByCustom(custom);
233
  }
234
  return this->UpdateByFetchAndReset();
235
236
237
238
}

bool cmCTestGIT::UpdateImpl()
{
239
  if (!this->UpdateInternal()) {
240
    return false;
241
  }
242

243
  std::string top_dir = this->FindTopDir();
244
  const char* git = this->CommandLineTool.c_str();
245
  const char* recursive = "--recursive";
246
  const char* sync_recursive = "--recursive";
247

248
  // Git < 1.6.5 did not support submodule --recursive
249
  if (this->GetGitVersion() < cmCTestGITVersion(1, 6, 5, 0)) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
250
    recursive = nullptr;
251
    // No need to require >= 1.6.5 if there are no submodules.
252
    if (cmSystemTools::FileExists(top_dir + "/.gitmodules")) {
253
      this->Log << "Git < 1.6.5 cannot update submodules recursively\n";
254
    }
255
  }
256

257
  // Git < 1.8.1 did not support sync --recursive
258
  if (this->GetGitVersion() < cmCTestGITVersion(1, 8, 1, 0)) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
259
    sync_recursive = nullptr;
260
    // No need to require >= 1.8.1 if there are no submodules.
261
    if (cmSystemTools::FileExists(top_dir + "/.gitmodules")) {
262
263
      this->Log << "Git < 1.8.1 cannot synchronize submodules recursively\n";
    }
264
  }
265

266
267
  OutputLogger submodule_out(this->Log, "submodule-out> ");
  OutputLogger submodule_err(this->Log, "submodule-err> ");
268
269
270
271
272

  bool ret;

  std::string init_submodules =
    this->CTest->GetCTestConfiguration("GITInitSubmodules");
273
  if (cmSystemTools::IsOn(init_submodules.c_str())) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
274
    char const* git_submodule_init[] = { git, "submodule", "init", nullptr };
275
276
277
    ret = this->RunChild(git_submodule_init, &submodule_out, &submodule_err,
                         top_dir.c_str());

278
    if (!ret) {
279
280
      return false;
    }
281
  }
282

283
  char const* git_submodule_sync[] = { git, "submodule", "sync",
Daniel Pfeifer's avatar
Daniel Pfeifer committed
284
                                       sync_recursive, nullptr };
285
286
287
  ret = this->RunChild(git_submodule_sync, &submodule_out, &submodule_err,
                       top_dir.c_str());

288
  if (!ret) {
289
    return false;
290
  }
291

Daniel Pfeifer's avatar
Daniel Pfeifer committed
292
  char const* git_submodule[] = { git, "submodule", "update", recursive,
Daniel Pfeifer's avatar
Daniel Pfeifer committed
293
                                  nullptr };
294
295
  return this->RunChild(git_submodule, &submodule_out, &submodule_err,
                        top_dir.c_str());
296
297
}

298
299
unsigned int cmCTestGIT::GetGitVersion()
{
300
  if (!this->CurrentGitVersion) {
301
    const char* git = this->CommandLineTool.c_str();
Daniel Pfeifer's avatar
Daniel Pfeifer committed
302
    char const* git_version[] = { git, "--version", nullptr };
303
304
305
    std::string version;
    OneLineParser version_out(this, "version-out> ", version);
    OutputLogger version_err(this->Log, "version-err> ");
306
307
308
309
    unsigned int v[4] = { 0, 0, 0, 0 };
    if (this->RunChild(git_version, &version_out, &version_err) &&
        sscanf(version.c_str(), "git version %u.%u.%u.%u", &v[0], &v[1], &v[2],
               &v[3]) >= 3) {
310
311
      this->CurrentGitVersion = cmCTestGITVersion(v[0], v[1], v[2], v[3]);
    }
312
  }
313
314
315
  return this->CurrentGitVersion;
}

316
317
318
319
320
321
322
323
324
325
/* Diff format:

   :src-mode dst-mode src-sha1 dst-sha1 status\0
   src-path\0
   [dst-path\0]

   The format is repeated for every file changed.  The [dst-path\0]
   line appears only for lines with status 'C' or 'R'.  See 'git help
   diff-tree' for details.
*/
326
class cmCTestGIT::DiffParser : public cmCTestVC::LineParser
327
328
{
public:
329
330
331
332
333
  DiffParser(cmCTestGIT* git, const char* prefix)
    : LineParser('\0', false)
    , GIT(git)
    , DiffField(DiffFieldNone)
  {
334
    this->SetLog(&git->Log, prefix);
335
  }
336
337
338

  typedef cmCTestGIT::Change Change;
  std::vector<Change> Changes;
339

340
341
protected:
  cmCTestGIT* GIT;
342
343
344
345
346
347
348
  enum DiffFieldType
  {
    DiffFieldNone,
    DiffFieldChange,
    DiffFieldSrc,
    DiffFieldDst
  };
349
350
351
352
  DiffFieldType DiffField;
  Change CurChange;

  void DiffReset()
353
  {
354
355
    this->DiffField = DiffFieldNone;
    this->Changes.clear();
356
  }
357

358
  bool ProcessLine() override
359
360
  {
    if (this->Line[0] == ':') {
361
362
      this->DiffField = DiffFieldChange;
      this->CurChange = Change();
363
364
    }
    if (this->DiffField == DiffFieldChange) {
365
      // :src-mode dst-mode src-sha1 dst-sha1 status
366
      if (this->Line[0] != ':') {
367
368
        this->DiffField = DiffFieldNone;
        return true;
369
370
371
      }
      const char* src_mode_first = this->Line.c_str() + 1;
      const char* src_mode_last = this->ConsumeField(src_mode_first);
372
      const char* dst_mode_first = this->ConsumeSpace(src_mode_last);
373
      const char* dst_mode_last = this->ConsumeField(dst_mode_first);
374
      const char* src_sha1_first = this->ConsumeSpace(dst_mode_last);
375
      const char* src_sha1_last = this->ConsumeField(src_sha1_first);
376
      const char* dst_sha1_first = this->ConsumeSpace(src_sha1_last);
377
378
379
380
      const char* dst_sha1_last = this->ConsumeField(dst_sha1_first);
      const char* status_first = this->ConsumeSpace(dst_sha1_last);
      const char* status_last = this->ConsumeField(status_first);
      if (status_first != status_last) {
381
382
        this->CurChange.Action = *status_first;
        this->DiffField = DiffFieldSrc;
383
      } else {
384
385
        this->DiffField = DiffFieldNone;
      }
386
    } else if (this->DiffField == DiffFieldSrc) {
387
      // src-path
388
      if (this->CurChange.Action == 'C') {
389
390
391
        // Convert copy to addition of destination.
        this->CurChange.Action = 'A';
        this->DiffField = DiffFieldDst;
392
      } else if (this->CurChange.Action == 'R') {
393
394
395
396
397
398
399
        // Convert rename to deletion of source and addition of destination.
        this->CurChange.Action = 'D';
        this->CurChange.Path = this->Line;
        this->Changes.push_back(this->CurChange);

        this->CurChange = Change('A');
        this->DiffField = DiffFieldDst;
400
      } else {
401
402
403
404
        this->CurChange.Path = this->Line;
        this->Changes.push_back(this->CurChange);
        this->DiffField = this->DiffFieldNone;
      }
405
    } else if (this->DiffField == DiffFieldDst) {
406
407
408
409
410
      // dst-path
      this->CurChange.Path = this->Line;
      this->Changes.push_back(this->CurChange);
      this->DiffField = this->DiffFieldNone;
    }
411
412
    return true;
  }
413
414

  const char* ConsumeSpace(const char* c)
415
416
417
  {
    while (*c && isspace(*c)) {
      ++c;
418
419
    }
    return c;
420
421
422
423
424
  }
  const char* ConsumeField(const char* c)
  {
    while (*c && !isspace(*c)) {
      ++c;
425
    }
426
427
    return c;
  }
428
429
430
431
432
433
434
435
436
437
438
439
};

/* Commit format:

   commit ...\n
   tree ...\n
   parent ...\n
   author ...\n
   committer ...\n
   \n
       Log message indented by (4) spaces\n
       (even blank lines have the spaces)\n
440
 [[
441
442
   \n
   [Diff format]
443
444
445
 OR
   \0
 ]]
446
447
448

   The header may have more fields.  See 'git help diff-tree'.
*/
449
class cmCTestGIT::CommitParser : public cmCTestGIT::DiffParser
450
451
{
public:
452
453
454
455
  CommitParser(cmCTestGIT* git, const char* prefix)
    : DiffParser(git, prefix)
    , Section(SectionHeader)
  {
456
    this->Separator = SectionSep[this->Section];
457
  }
458
459
460

private:
  typedef cmCTestGIT::Revision Revision;
461
462
463
464
465
466
467
  enum SectionType
  {
    SectionHeader,
    SectionBody,
    SectionDiff,
    SectionCount
  };
468
469
470
471
472
473
474
475
476
477
  static char const SectionSep[SectionCount];
  SectionType Section;
  Revision Rev;

  struct Person
  {
    std::string Name;
    std::string EMail;
    unsigned long Time;
    long TimeZone;
478
479
480
481
482
483
484
    Person()
      : Name()
      , EMail()
      , Time(0)
      , TimeZone(0)
    {
    }
485
486
487
  };

  void ParsePerson(const char* str, Person& person)
488
  {
489
490
    // Person Name <person@domain.com> 1234567890 +0000
    const char* c = str;
491
492
493
    while (*c && isspace(*c)) {
      ++c;
    }
494
495

    const char* name_first = c;
496
497
498
    while (*c && *c != '<') {
      ++c;
    }
499
    const char* name_last = c;
500
501
502
503
    while (name_last != name_first && isspace(*(name_last - 1))) {
      --name_last;
    }
    person.Name.assign(name_first, name_last - name_first);
504

505
506
507
508
509
510
    const char* email_first = *c ? ++c : c;
    while (*c && *c != '>') {
      ++c;
    }
    const char* email_last = *c ? c++ : c;
    person.EMail.assign(email_first, email_last - email_first);
511

Daniel Pfeifer's avatar
Daniel Pfeifer committed
512
513
    person.Time = strtoul(c, const_cast<char**>(&c), 10);
    person.TimeZone = strtol(c, const_cast<char**>(&c), 10);
514
  }
515

516
  bool ProcessLine() override
517
518
519
  {
    if (this->Line.empty()) {
      if (this->Section == SectionBody && this->LineEnd == '\0') {
520
521
        // Skip SectionDiff
        this->NextSection();
522
      }
523
524
525
526
527
528
529
530
531
532
533
534
535
536
      this->NextSection();
    } else {
      switch (this->Section) {
        case SectionHeader:
          this->DoHeaderLine();
          break;
        case SectionBody:
          this->DoBodyLine();
          break;
        case SectionDiff:
          this->DiffParser::ProcessLine();
          break;
        case SectionCount:
          break; // never happens
537
538
      }
    }
539
540
    return true;
  }
541
542

  void NextSection()
543
544
  {
    this->Section = SectionType((this->Section + 1) % SectionCount);
545
    this->Separator = SectionSep[this->Section];
546
    if (this->Section == SectionHeader) {
547
548
549
550
      this->GIT->DoRevision(this->Rev, this->Changes);
      this->Rev = Revision();
      this->DiffReset();
    }
551
  }
552
553

  void DoHeaderLine()
554
  {
555
    // Look for header fields that we need.
556
    if (cmHasLiteralPrefix(this->Line, "commit ")) {
557
      this->Rev.Rev = this->Line.c_str() + 7;
558
    } else if (cmHasLiteralPrefix(this->Line, "author ")) {
559
      Person author;
560
      this->ParsePerson(this->Line.c_str() + 7, author);
561
      this->Rev.Author = author.Name;
562
      this->Rev.EMail = author.EMail;
563
      this->Rev.Date = this->FormatDateTime(author);
564
    } else if (cmHasLiteralPrefix(this->Line, "committer ")) {
565
      Person committer;
566
      this->ParsePerson(this->Line.c_str() + 10, committer);
567
568
      this->Rev.Committer = committer.Name;
      this->Rev.CommitterEMail = committer.EMail;
569
      this->Rev.CommitDate = this->FormatDateTime(committer);
570
    }
571
  }
572
573

  void DoBodyLine()
574
  {
575
    // Commit log lines are indented by 4 spaces.
576
    if (this->Line.size() >= 4) {
577
578
      this->Rev.Log += this->Line.substr(4);
    }
579
580
    this->Rev.Log += "\n";
  }
581
582

  std::string FormatDateTime(Person const& person)
583
  {
584
585
586
587
588
    // Convert the time to a human-readable format that is also easy
    // to machine-parse: "CCYY-MM-DD hh:mm:ss".
    time_t seconds = static_cast<time_t>(person.Time);
    struct tm* t = gmtime(&seconds);
    char dt[1024];
589
590
    sprintf(dt, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900,
            t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
591
592
593
594
    std::string out = dt;

    // Add the time-zone field "+zone" or "-zone".
    char tz[32];
595
    if (person.TimeZone >= 0) {
596
      sprintf(tz, " +%04ld", person.TimeZone);
597
    } else {
598
      sprintf(tz, " -%04ld", -person.TimeZone);
599
    }
600
601
    out += tz;
    return out;
602
  }
603
604
};

605
606
char const cmCTestGIT::CommitParser::SectionSep[SectionCount] = { '\n', '\n',
                                                                  '\0' };
607

608
bool cmCTestGIT::LoadRevisions()
609
610
611
612
{
  // Use 'git rev-list ... | git diff-tree ...' to get revisions.
  std::string range = this->OldRevision + ".." + this->NewRevision;
  const char* git = this->CommandLineTool.c_str();
613
  const char* git_rev_list[] = { git,           "rev-list", "--reverse",
Daniel Pfeifer's avatar
Daniel Pfeifer committed
614
                                 range.c_str(), "--",       nullptr };
615
616
  const char* git_diff_tree[] = {
    git,  "diff-tree",    "--stdin",          "--always", "-z",
Daniel Pfeifer's avatar
Daniel Pfeifer committed
617
    "-r", "--pretty=raw", "--encoding=utf-8", nullptr
618
  };
619
620
621
622
623
624
625
626
627
628
  this->Log << this->ComputeCommandLine(git_rev_list) << " | "
            << this->ComputeCommandLine(git_diff_tree) << "\n";

  cmsysProcess* cp = cmsysProcess_New();
  cmsysProcess_AddCommand(cp, git_rev_list);
  cmsysProcess_AddCommand(cp, git_diff_tree);
  cmsysProcess_SetWorkingDirectory(cp, this->SourceDirectory.c_str());

  CommitParser out(this, "dt-out> ");
  OutputLogger err(this->Log, "dt-err> ");
629
  this->RunProcess(cp, &out, &err, cmProcessOutput::UTF8);
630
631
632
633
634

  // Send one extra zero-byte to terminate the last record.
  out.Process("", 1);

  cmsysProcess_Delete(cp);
635
  return true;
636
637
}

638
bool cmCTestGIT::LoadModifications()
639
640
641
{
  const char* git = this->CommandLineTool.c_str();

642
  // Use 'git update-index' to refresh the index w.r.t. the work tree.
Daniel Pfeifer's avatar
Daniel Pfeifer committed
643
  const char* git_update_index[] = { git, "update-index", "--refresh",
Daniel Pfeifer's avatar
Daniel Pfeifer committed
644
                                     nullptr };
645
646
  OutputLogger ui_out(this->Log, "ui-out> ");
  OutputLogger ui_err(this->Log, "ui-err> ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
647
  this->RunChild(git_update_index, &ui_out, &ui_err, nullptr,
648
                 cmProcessOutput::UTF8);
649
650

  // Use 'git diff-index' to get modified files.
Daniel Pfeifer's avatar
Daniel Pfeifer committed
651
  const char* git_diff_index[] = { git,    "diff-index", "-z",
Daniel Pfeifer's avatar
Daniel Pfeifer committed
652
                                   "HEAD", "--",         nullptr };
653
654
  DiffParser out(this, "di-out> ");
  OutputLogger err(this->Log, "di-err> ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
655
  this->RunChild(git_diff_index, &out, &err, nullptr, cmProcessOutput::UTF8);
656

657
658
  for (Change const& c : out.Changes) {
    this->DoModification(PathModified, c.Path);
659
  }
660
  return true;
661
}