cmCTestCVS.cxx 8.59 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 "cmCTestCVS.h"

5
#include "cmCTest.h"
6
#include "cmProcessTools.h"
7
#include "cmSystemTools.h"
8
#include "cmXMLWriter.h"
9

10 11
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
12
#include <utility>
13

14 15
cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log)
  : cmCTestVC(ct, log)
16 17 18
{
}

wahikihiki's avatar
wahikihiki committed
19
cmCTestCVS::~cmCTestCVS() = default;
20

21
class cmCTestCVS::UpdateParser : public cmCTestVC::LineParser
22 23
{
public:
24 25 26
  UpdateParser(cmCTestCVS* cvs, const char* prefix)
    : CVS(cvs)
  {
27 28 29 30 31 32
    this->SetLog(&cvs->Log, prefix);
    // See "man cvs", section "update output".
    this->RegexFileUpdated.compile("^([UP])  *(.*)");
    this->RegexFileModified.compile("^([MRA])  *(.*)");
    this->RegexFileConflicting.compile("^([C])  *(.*)");
    this->RegexFileRemoved1.compile(
33
      "cvs[^ ]* update: `?([^']*)'? is no longer in the repository");
34
    this->RegexFileRemoved2.compile(
35 36
      "cvs[^ ]* update: "
      "warning: `?([^']*)'? is not \\(any longer\\) pertinent");
37 38
  }

39 40 41 42 43 44 45 46
private:
  cmCTestCVS* CVS;
  cmsys::RegularExpression RegexFileUpdated;
  cmsys::RegularExpression RegexFileModified;
  cmsys::RegularExpression RegexFileConflicting;
  cmsys::RegularExpression RegexFileRemoved1;
  cmsys::RegularExpression RegexFileRemoved2;

47
  bool ProcessLine() override
48 49
  {
    if (this->RegexFileUpdated.find(this->Line)) {
50
      this->DoFile(PathUpdated, this->RegexFileUpdated.match(2));
51
    } else if (this->RegexFileModified.find(this->Line)) {
52
      this->DoFile(PathModified, this->RegexFileModified.match(2));
53
    } else if (this->RegexFileConflicting.find(this->Line)) {
54
      this->DoFile(PathConflicting, this->RegexFileConflicting.match(2));
55
    } else if (this->RegexFileRemoved1.find(this->Line)) {
56
      this->DoFile(PathUpdated, this->RegexFileRemoved1.match(1));
57
    } else if (this->RegexFileRemoved2.find(this->Line)) {
58 59
      this->DoFile(PathUpdated, this->RegexFileRemoved2.match(1));
    }
60 61
    return true;
  }
62 63

  void DoFile(PathStatus status, std::string const& file)
64
  {
65 66 67
    std::string dir = cmSystemTools::GetFilenamePath(file);
    std::string name = cmSystemTools::GetFilenameName(file);
    this->CVS->Dirs[dir][name] = status;
68
  }
69 70 71 72 73 74
};

bool cmCTestCVS::UpdateImpl()
{
  // Get user-specified update options.
  std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
75
  if (opts.empty()) {
76
    opts = this->CTest->GetCTestConfiguration("CVSUpdateOptions");
77
    if (opts.empty()) {
78 79
      opts = "-dP";
    }
80
  }
81
  std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str());
82 83

  // Specify the start time for nightly testing.
84
  if (this->CTest->GetTestModel() == cmCTest::NIGHTLY) {
85
    args.push_back("-D" + this->GetNightlyTime() + " UTC");
86
  }
87 88 89 90 91 92

  // Run "cvs update" to update the work tree.
  std::vector<char const*> cvs_update;
  cvs_update.push_back(this->CommandLineTool.c_str());
  cvs_update.push_back("-z3");
  cvs_update.push_back("update");
93 94
  for (std::string const& arg : args) {
    cvs_update.push_back(arg.c_str());
95
  }
Daniel Pfeifer's avatar
Daniel Pfeifer committed
96
  cvs_update.push_back(nullptr);
97 98 99 100 101 102

  UpdateParser out(this, "up-out> ");
  UpdateParser err(this, "up-err> ");
  return this->RunUpdateCommand(&cvs_update[0], &out, &err);
}

103
class cmCTestCVS::LogParser : public cmCTestVC::LineParser
104 105 106
{
public:
  typedef cmCTestCVS::Revision Revision;
107 108 109 110 111
  LogParser(cmCTestCVS* cvs, const char* prefix, std::vector<Revision>& revs)
    : CVS(cvs)
    , Revisions(revs)
    , Section(SectionHeader)
  {
112 113
    this->SetLog(&cvs->Log, prefix);
    this->RegexRevision.compile("^revision +([^ ]*) *$");
114 115
    this->RegexBranches.compile("^branches: .*$");
    this->RegexPerson.compile("^date: +([^;]+); +author: +([^;]+);");
116 117
  }

118 119 120 121 122 123
private:
  cmCTestCVS* CVS;
  std::vector<Revision>& Revisions;
  cmsys::RegularExpression RegexRevision;
  cmsys::RegularExpression RegexBranches;
  cmsys::RegularExpression RegexPerson;
124 125 126 127 128 129
  enum SectionType
  {
    SectionHeader,
    SectionRevisions,
    SectionEnd
  };
130 131 132
  SectionType Section;
  Revision Rev;

133
  bool ProcessLine() override
134
  {
135 136 137
    if (this->Line ==
        ("======================================="
         "======================================")) {
138
      // This line ends the revision list.
139
      if (this->Section == SectionRevisions) {
140 141
        this->FinishRevision();
      }
142 143
      this->Section = SectionEnd;
    } else if (this->Line == "----------------------------") {
144
      // This line divides revisions from the header and each other.
145
      if (this->Section == SectionHeader) {
146
        this->Section = SectionRevisions;
147
      } else if (this->Section == SectionRevisions) {
148 149
        this->FinishRevision();
      }
150 151
    } else if (this->Section == SectionRevisions) {
      if (!this->Rev.Log.empty()) {
152 153 154
        // Continue the existing log.
        this->Rev.Log += this->Line;
        this->Rev.Log += "\n";
155 156
      } else if (this->Rev.Rev.empty() &&
                 this->RegexRevision.find(this->Line)) {
157
        this->Rev.Rev = this->RegexRevision.match(1);
158 159
      } else if (this->Rev.Date.empty() &&
                 this->RegexPerson.find(this->Line)) {
160 161
        this->Rev.Date = this->RegexPerson.match(1);
        this->Rev.Author = this->RegexPerson.match(2);
162
      } else if (!this->RegexBranches.find(this->Line)) {
163 164 165 166 167
        // Start the log.
        this->Rev.Log += this->Line;
        this->Rev.Log += "\n";
      }
    }
168 169
    return this->Section != SectionEnd;
  }
170 171

  void FinishRevision()
172 173
  {
    if (!this->Rev.Rev.empty()) {
174
      // Record this revision.
175
      /* clang-format off */
176 177 178
      this->CVS->Log << "Found revision " << this->Rev.Rev << "\n"
                     << "  author = " << this->Rev.Author << "\n"
                     << "  date = " << this->Rev.Date << "\n";
179
      /* clang-format on */
180 181 182
      this->Revisions.push_back(this->Rev);

      // We only need two revisions.
183
      if (this->Revisions.size() >= 2) {
184 185 186
        this->Section = SectionEnd;
      }
    }
187 188
    this->Rev = Revision();
  }
189 190 191 192 193 194
};

std::string cmCTestCVS::ComputeBranchFlag(std::string const& dir)
{
  // Compute the tag file location for this directory.
  std::string tagFile = this->SourceDirectory;
195
  if (!dir.empty()) {
196 197
    tagFile += "/";
    tagFile += dir;
198
  }
199 200 201 202
  tagFile += "/CVS/Tag";

  // Lookup the branch in the tag file, if any.
  std::string tagLine;
203
  cmsys::ifstream tagStream(tagFile.c_str());
204 205
  if (tagStream && cmSystemTools::GetLineFromStream(tagStream, tagLine) &&
      tagLine.size() > 1 && tagLine[0] == 'T') {
206 207 208 209
    // Use the branch specified in the tag file.
    std::string flag = "-r";
    flag += tagLine.substr(1);
    return flag;
210
  }
211 212
  // Use the default branch.
  return "-b";
213 214
}

215
void cmCTestCVS::LoadRevisions(std::string const& file, const char* branchFlag,
216 217 218 219 220 221
                               std::vector<Revision>& revisions)
{
  cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush);

  // Run "cvs log" to get revisions of this file on this branch.
  const char* cvs = this->CommandLineTool.c_str();
Daniel Pfeifer's avatar
Daniel Pfeifer committed
222 223 224
  const char* cvs_log[] = {
    cvs, "log", "-N", branchFlag, file.c_str(), nullptr
  };
225 226 227 228 229 230

  LogParser out(this, "log-out> ", revisions);
  OutputLogger err(this->Log, "log-err> ");
  this->RunChild(cvs_log, &out, &err);
}

231
void cmCTestCVS::WriteXMLDirectory(cmXMLWriter& xml, std::string const& path,
232 233
                                   Directory const& dir)
{
234
  const char* slash = path.empty() ? "" : "/";
235 236
  xml.StartElement("Directory");
  xml.Element("Name", path);
237 238 239 240 241 242

  // Lookup the branch checked out in the working tree.
  std::string branchFlag = this->ComputeBranchFlag(path);

  // Load revisions and write an entry for each file in this directory.
  std::vector<Revision> revisions;
243 244
  for (auto const& fi : dir) {
    std::string full = path + slash + fi.first;
245 246 247

    // Load two real or unknown revisions.
    revisions.clear();
248
    if (fi.second != PathUpdated) {
249 250 251
      // For local modifications the current rev is unknown and the
      // prior rev is the latest from cvs.
      revisions.push_back(this->Unknown);
252
    }
253 254 255 256
    this->LoadRevisions(full, branchFlag.c_str(), revisions);
    revisions.resize(2, this->Unknown);

    // Write the entry for this file with these revisions.
257 258
    File f(fi.second, &revisions[0], &revisions[1]);
    this->WriteXMLEntry(xml, path, fi.first, full, f);
259
  }
260
  xml.EndElement(); // Directory
261 262
}

263
bool cmCTestCVS::WriteXMLUpdates(cmXMLWriter& xml)
264 265 266
{
  cmCTestLog(this->CTest, HANDLER_OUTPUT,
             "   Gathering version information (one . per updated file):\n"
267 268
             "    "
               << std::flush);
269

270 271
  for (auto const& d : this->Dirs) {
    this->WriteXMLDirectory(xml, d.first, d.second);
272
  }
273 274 275 276 277

  cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);

  return true;
}