| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmCTestUpdateHandler.h" |
| |
| #include "cmAlgorithms.h" |
| #include "cmCLocaleEnvironmentScope.h" |
| #include "cmCTest.h" |
| #include "cmCTestBZR.h" |
| #include "cmCTestCVS.h" |
| #include "cmCTestGIT.h" |
| #include "cmCTestHG.h" |
| #include "cmCTestP4.h" |
| #include "cmCTestSVN.h" |
| #include "cmCTestVC.h" |
| #include "cmGeneratedFileStream.h" |
| #include "cmSystemTools.h" |
| #include "cmVersion.h" |
| #include "cmXMLWriter.h" |
| |
| #include <chrono> |
| #include <memory> // IWYU pragma: keep |
| #include <sstream> |
| |
| static const char* cmCTestUpdateHandlerUpdateStrings[] = { |
| "Unknown", "CVS", "SVN", "BZR", "GIT", "HG", "P4" |
| }; |
| |
| static const char* cmCTestUpdateHandlerUpdateToString(int type) |
| { |
| if (type < cmCTestUpdateHandler::e_UNKNOWN || |
| type >= cmCTestUpdateHandler::e_LAST) { |
| return cmCTestUpdateHandlerUpdateStrings[cmCTestUpdateHandler::e_UNKNOWN]; |
| } |
| return cmCTestUpdateHandlerUpdateStrings[type]; |
| } |
| |
| cmCTestUpdateHandler::cmCTestUpdateHandler() = default; |
| |
| void cmCTestUpdateHandler::Initialize() |
| { |
| this->Superclass::Initialize(); |
| this->UpdateCommand.clear(); |
| this->UpdateType = e_CVS; |
| } |
| |
| int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type) |
| { |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "Determine update type from command: " |
| << cmd << " and type: " << type << std::endl, |
| this->Quiet); |
| if (type && *type) { |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "Type specified: " << type << std::endl, this->Quiet); |
| std::string stype = cmSystemTools::LowerCase(type); |
| if (stype.find("cvs") != std::string::npos) { |
| return cmCTestUpdateHandler::e_CVS; |
| } |
| if (stype.find("svn") != std::string::npos) { |
| return cmCTestUpdateHandler::e_SVN; |
| } |
| if (stype.find("bzr") != std::string::npos) { |
| return cmCTestUpdateHandler::e_BZR; |
| } |
| if (stype.find("git") != std::string::npos) { |
| return cmCTestUpdateHandler::e_GIT; |
| } |
| if (stype.find("hg") != std::string::npos) { |
| return cmCTestUpdateHandler::e_HG; |
| } |
| if (stype.find("p4") != std::string::npos) { |
| return cmCTestUpdateHandler::e_P4; |
| } |
| } else { |
| cmCTestOptionalLog( |
| this->CTest, DEBUG, |
| "Type not specified, check command: " << cmd << std::endl, this->Quiet); |
| std::string stype = cmSystemTools::LowerCase(cmd); |
| if (stype.find("cvs") != std::string::npos) { |
| return cmCTestUpdateHandler::e_CVS; |
| } |
| if (stype.find("svn") != std::string::npos) { |
| return cmCTestUpdateHandler::e_SVN; |
| } |
| if (stype.find("bzr") != std::string::npos) { |
| return cmCTestUpdateHandler::e_BZR; |
| } |
| if (stype.find("git") != std::string::npos) { |
| return cmCTestUpdateHandler::e_GIT; |
| } |
| if (stype.find("hg") != std::string::npos) { |
| return cmCTestUpdateHandler::e_HG; |
| } |
| if (stype.find("p4") != std::string::npos) { |
| return cmCTestUpdateHandler::e_P4; |
| } |
| } |
| return cmCTestUpdateHandler::e_UNKNOWN; |
| } |
| |
| // clearly it would be nice if this were broken up into a few smaller |
| // functions and commented... |
| int cmCTestUpdateHandler::ProcessHandler() |
| { |
| // Make sure VCS tool messages are in English so we can parse them. |
| cmCLocaleEnvironmentScope fixLocale; |
| static_cast<void>(fixLocale); |
| |
| // Get source dir |
| const char* sourceDirectory = this->GetOption("SourceDirectory"); |
| if (!sourceDirectory) { |
| cmCTestLog(this->CTest, ERROR_MESSAGE, |
| "Cannot find SourceDirectory key in the DartConfiguration.tcl" |
| << std::endl); |
| return -1; |
| } |
| |
| cmGeneratedFileStream ofs; |
| if (!this->CTest->GetShowOnly()) { |
| this->StartLogFile("Update", ofs); |
| } |
| |
| cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, |
| " Updating the repository: " << sourceDirectory |
| << std::endl, |
| this->Quiet); |
| |
| if (!this->SelectVCS()) { |
| return -1; |
| } |
| |
| cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, |
| " Use " |
| << cmCTestUpdateHandlerUpdateToString(this->UpdateType) |
| << " repository type" << std::endl; |
| , this->Quiet); |
| |
| // Create an object to interact with the VCS tool. |
| std::unique_ptr<cmCTestVC> vc; |
| switch (this->UpdateType) { |
| case e_CVS: |
| vc = cm::make_unique<cmCTestCVS>(this->CTest, ofs); |
| break; |
| case e_SVN: |
| vc = cm::make_unique<cmCTestSVN>(this->CTest, ofs); |
| break; |
| case e_BZR: |
| vc = cm::make_unique<cmCTestBZR>(this->CTest, ofs); |
| break; |
| case e_GIT: |
| vc = cm::make_unique<cmCTestGIT>(this->CTest, ofs); |
| break; |
| case e_HG: |
| vc = cm::make_unique<cmCTestHG>(this->CTest, ofs); |
| break; |
| case e_P4: |
| vc = cm::make_unique<cmCTestP4>(this->CTest, ofs); |
| break; |
| default: |
| vc = cm::make_unique<cmCTestVC>(this->CTest, ofs); |
| break; |
| } |
| vc->SetCommandLineTool(this->UpdateCommand); |
| vc->SetSourceDirectory(sourceDirectory); |
| |
| // Cleanup the working tree. |
| vc->Cleanup(); |
| |
| // |
| // Now update repository and remember what files were updated |
| // |
| cmGeneratedFileStream os; |
| if (!this->StartResultingXML(cmCTest::PartUpdate, "Update", os)) { |
| cmCTestLog(this->CTest, ERROR_MESSAGE, |
| "Cannot open log file" << std::endl); |
| return -1; |
| } |
| std::string start_time = this->CTest->CurrentTime(); |
| auto start_time_time = std::chrono::system_clock::now(); |
| auto elapsed_time_start = std::chrono::steady_clock::now(); |
| |
| bool updated = vc->Update(); |
| std::string buildname = |
| cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName")); |
| |
| cmXMLWriter xml(os); |
| xml.StartDocument(); |
| xml.StartElement("Update"); |
| xml.Attribute("mode", "Client"); |
| xml.Attribute("Generator", |
| std::string("ctest-") + cmVersion::GetCMakeVersion()); |
| xml.Element("Site", this->CTest->GetCTestConfiguration("Site")); |
| xml.Element("BuildName", buildname); |
| xml.Element("BuildStamp", |
| this->CTest->GetCurrentTag() + "-" + |
| this->CTest->GetTestModelString()); |
| xml.Element("StartDateTime", start_time); |
| xml.Element("StartTime", start_time_time); |
| xml.Element("UpdateCommand", vc->GetUpdateCommandLine()); |
| xml.Element("UpdateType", |
| cmCTestUpdateHandlerUpdateToString(this->UpdateType)); |
| std::string changeId = this->CTest->GetCTestConfiguration("ChangeId"); |
| if (!changeId.empty()) { |
| xml.Element("ChangeId", changeId); |
| } |
| |
| bool loadedMods = vc->WriteXML(xml); |
| |
| int localModifications = 0; |
| int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated); |
| if (numUpdated) { |
| cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, |
| " Found " << numUpdated << " updated files\n", |
| this->Quiet); |
| } |
| if (int numModified = vc->GetPathCount(cmCTestVC::PathModified)) { |
| cmCTestOptionalLog( |
| this->CTest, HANDLER_OUTPUT, |
| " Found " << numModified << " locally modified files\n", this->Quiet); |
| localModifications += numModified; |
| } |
| if (int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting)) { |
| cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, |
| " Found " << numConflicting << " conflicting files\n", |
| this->Quiet); |
| localModifications += numConflicting; |
| } |
| |
| cmCTestOptionalLog(this->CTest, DEBUG, "End" << std::endl, this->Quiet); |
| std::string end_time = this->CTest->CurrentTime(); |
| xml.Element("EndDateTime", end_time); |
| xml.Element("EndTime", std::chrono::system_clock::now()); |
| xml.Element("ElapsedMinutes", |
| std::chrono::duration_cast<std::chrono::minutes>( |
| std::chrono::steady_clock::now() - elapsed_time_start) |
| .count()); |
| |
| xml.StartElement("UpdateReturnStatus"); |
| if (localModifications) { |
| xml.Content("Update error: " |
| "There are modified or conflicting files in the repository"); |
| cmCTestLog(this->CTest, ERROR_MESSAGE, |
| " There are modified or conflicting files in the repository" |
| << std::endl); |
| } |
| if (!updated) { |
| xml.Content("Update command failed:\n"); |
| xml.Content(vc->GetUpdateCommandLine()); |
| cmCTestLog(this->CTest, HANDLER_OUTPUT, |
| " Update command failed: " << vc->GetUpdateCommandLine() |
| << "\n"); |
| } |
| xml.EndElement(); // UpdateReturnStatus |
| xml.EndElement(); // Update |
| xml.EndDocument(); |
| return updated && loadedMods ? numUpdated : -1; |
| } |
| |
| int cmCTestUpdateHandler::DetectVCS(const char* dir) |
| { |
| std::string sourceDirectory = dir; |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "Check directory: " << sourceDirectory << std::endl, |
| this->Quiet); |
| sourceDirectory += "/.svn"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_SVN; |
| } |
| sourceDirectory = dir; |
| sourceDirectory += "/CVS"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_CVS; |
| } |
| sourceDirectory = dir; |
| sourceDirectory += "/.bzr"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_BZR; |
| } |
| sourceDirectory = dir; |
| sourceDirectory += "/.git"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_GIT; |
| } |
| sourceDirectory = dir; |
| sourceDirectory += "/.hg"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_HG; |
| } |
| sourceDirectory = dir; |
| sourceDirectory += "/.p4"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_P4; |
| } |
| sourceDirectory = dir; |
| sourceDirectory += "/.p4config"; |
| if (cmSystemTools::FileExists(sourceDirectory)) { |
| return cmCTestUpdateHandler::e_P4; |
| } |
| return cmCTestUpdateHandler::e_UNKNOWN; |
| } |
| |
| bool cmCTestUpdateHandler::SelectVCS() |
| { |
| // Get update command |
| this->UpdateCommand = this->CTest->GetCTestConfiguration("UpdateCommand"); |
| |
| // Detect the VCS managing the source tree. |
| this->UpdateType = this->DetectVCS(this->GetOption("SourceDirectory")); |
| if (this->UpdateType == e_UNKNOWN) { |
| // The source tree does not have a recognized VCS. Check the |
| // configuration value or command name. |
| this->UpdateType = this->DetermineType( |
| this->UpdateCommand.c_str(), |
| this->CTest->GetCTestConfiguration("UpdateType").c_str()); |
| } |
| |
| // If no update command was specified, lookup one for this VCS tool. |
| if (this->UpdateCommand.empty()) { |
| const char* key = nullptr; |
| switch (this->UpdateType) { |
| case e_CVS: |
| key = "CVSCommand"; |
| break; |
| case e_SVN: |
| key = "SVNCommand"; |
| break; |
| case e_BZR: |
| key = "BZRCommand"; |
| break; |
| case e_GIT: |
| key = "GITCommand"; |
| break; |
| case e_HG: |
| key = "HGCommand"; |
| break; |
| case e_P4: |
| key = "P4Command"; |
| break; |
| default: |
| break; |
| } |
| if (key) { |
| this->UpdateCommand = this->CTest->GetCTestConfiguration(key); |
| } |
| if (this->UpdateCommand.empty()) { |
| std::ostringstream e; |
| e << "Cannot find UpdateCommand "; |
| if (key) { |
| e << "or " << key; |
| } |
| e << " configuration key."; |
| cmCTestLog(this->CTest, ERROR_MESSAGE, e.str() << std::endl); |
| return false; |
| } |
| } |
| |
| return true; |
| } |