View Issue Details [ Jump to Notes ] | [ Print ] | ||||||||
ID | Project | Category | View Status | Date Submitted | Last Update | ||||
0006857 | CMake | CTest | public | 2008-04-21 09:00 | 2009-12-16 14:28 | ||||
Reporter | TomPouce25 | ||||||||
Assigned To | Brad King | ||||||||
Priority | normal | Severity | feature | Reproducibility | N/A | ||||
Status | closed | Resolution | fixed | ||||||
Platform | OS | OS Version | |||||||
Product Version | CMake-2-6 | ||||||||
Target Version | Fixed in Version | ||||||||
Summary | 0006857: Feature request: More UPDATE_TYPE (e.g. bzr) in CTest | ||||||||
Description | It would be great if CTest could support other version control systems. Currently cvs and svn are supported. Supporting bzr would be interesting. | ||||||||
Tags | No tags attached. | ||||||||
Attached Files | ![]() Binary files cmake-2.6.1/cmake.vtg and cmake-2.6.1-patched/cmake.vtg differ diff -urN cmake-2.6.1/Modules/CTest.cmake cmake-2.6.1-patched/Modules/CTest.cmake --- cmake-2.6.1/Modules/CTest.cmake 2008-08-01 17:34:50.000000000 +0200 +++ cmake-2.6.1-patched/Modules/CTest.cmake 2008-08-10 21:29:38.000000000 +0200 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -129,6 +134,11 @@ IF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") + IF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") ENDIF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") ENDIF(UPDATE_TYPE MATCHES "[Cc][Vv][Ss]") @@ -194,8 +204,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE diff -urN cmake-2.6.1/Source/CTest/cmCTestUpdateCommand.cxx cmake-2.6.1-patched/Source/CTest/cmCTestUpdateCommand.cxx --- cmake-2.6.1/Source/CTest/cmCTestUpdateCommand.cxx 2008-08-01 17:34:54.000000000 +0200 +++ cmake-2.6.1-patched/Source/CTest/cmCTestUpdateCommand.cxx 2008-08-20 14:56:39.000000000 +0200 @@ -48,6 +48,10 @@ "SVNCommand", "CTEST_SVN_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); const char* initialCheckoutCommand = this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND"); diff -urN cmake-2.6.1/Source/CTest/cmCTestUpdateHandler.cxx cmake-2.6.1-patched/Source/CTest/cmCTestUpdateHandler.cxx --- cmake-2.6.1/Source/CTest/cmCTestUpdateHandler.cxx 2008-08-01 17:34:54.000000000 +0200 +++ cmake-2.6.1-patched/Source/CTest/cmCTestUpdateHandler.cxx 2008-08-21 00:34:46.000000000 +0200 @@ -17,6 +17,8 @@ #include "cmCTestUpdateHandler.h" +#include "cm_curl.h" + #include "cmCTest.h" #include "cmake.h" #include "cmMakefile.h" @@ -43,7 +45,8 @@ { "Unknown", "CVS", - "SVN" + "SVN", + "BZR" }; static const char* cmCTestUpdateHandlerUpdateToString(int type) @@ -177,6 +180,109 @@ int MinRevision; int MaxRevision; }; +//---------------------------------------------------------------------- +//********************************************************************** +class cmCTestUpdateHandlerBZRXMLParser : public cmXMLParser +{ +public: + struct t_CommitLog + { + int Revision; + std::string Author; + std::string Date; + std::string Message; + }; + cmCTestUpdateHandlerBZRXMLParser(cmCTestUpdateHandler* up) + : cmXMLParser(), UpdateHandler(up), MinRevision(-1), MaxRevision(-1) + { + } + + int Parse(const char* str) + { + this->MinRevision = -1; + this->MaxRevision = -1; + int res = this->cmXMLParser::Parse(str); + if ( this->MinRevision == -1 || this->MaxRevision == -1 ) + { + return 0; + } + return res; + } + + typedef std::vector<t_CommitLog> t_VectorOfCommits; + + t_VectorOfCommits* GetCommits() { return &this->Commits; } + int GetMinRevision() { return this->MinRevision; } + int GetMaxRevision() { return this->MaxRevision; } + +protected: + void StartElement(const char* name, const char** atts) + { + if ( strcmp(name, "log") == 0 ) + { + this->CommitLog = t_CommitLog(); + } + this->CharacterData.erase( + this->CharacterData.begin(), this->CharacterData.end()); + } + void EndElement(const char* name) + { + if ( strcmp(name, "log") == 0 ) + { + cmCTestLog(this->UpdateHandler->GetCTestInstance(), + HANDLER_VERBOSE_OUTPUT, + "\tRevision: " << this->CommitLog.Revision<< std::endl + << "\tAuthor: " << this->CommitLog.Author.c_str() << std::endl + << "\tDate: " << this->CommitLog.Date.c_str() << std::endl + << "\tMessage: " << this->CommitLog.Message.c_str() << std::endl); + this->Commits.push_back(this->CommitLog); + } + else if ( strcmp(name, "committer") == 0 ) + { + this->CommitLog.Author.assign(&(*(this->CharacterData.begin())), + this->CharacterData.size()); + } + else if ( strcmp(name, "timestamp") == 0 ) + { + this->CommitLog.Date.assign(&(*(this->CharacterData.begin())), + this->CharacterData.size()); + } + else if ( strcmp(name, "message") == 0 ) + { + this->CommitLog.Message.assign(&(*(this->CharacterData.begin())), + this->CharacterData.size()); + } + else if ( strcmp(name, "revno") == 0 ) + { + this->CommitLog.Revision = atoi(&(*this->CharacterData.begin())); + if ( this->MinRevision < 0 || + this->MinRevision > this->CommitLog.Revision ) + { + this->MinRevision = this->CommitLog.Revision; + } + if ( this->MaxRevision < 0 || + this->MaxRevision < this->CommitLog.Revision ) + { + this->MaxRevision = this->CommitLog.Revision; + } + } + this->CharacterData.erase(this->CharacterData.begin(), + this->CharacterData.end()); + } + void CharacterDataHandler(const char* data, int length) + { + this->CharacterData.insert(this->CharacterData.end(), data, data+length); + } + +private: + std::vector<char> CharacterData; + cmCTestUpdateHandler* UpdateHandler; + t_CommitLog CommitLog; + + t_VectorOfCommits Commits; + int MinRevision; + int MaxRevision; +}; //********************************************************************** //---------------------------------------------------------------------- @@ -208,6 +314,10 @@ { return cmCTestUpdateHandler::e_SVN; } + if ( stype.find("bzr") != std::string::npos ) + { + return cmCTestUpdateHandler::e_BZR; + } } else { @@ -222,6 +332,10 @@ { return cmCTestUpdateHandler::e_SVN; } + if ( stype.find("bzr") != std::string::npos ) + { + return cmCTestUpdateHandler::e_BZR; + } } std::string sourceDirectory = this->GetOption("SourceDirectory"); cmCTestLog(this->CTest, DEBUG, "Check directory: " @@ -237,6 +351,12 @@ { return cmCTestUpdateHandler::e_CVS; } + sourceDirectory = this->GetOption("SourceDirectory"); + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } return cmCTestUpdateHandler::e_UNKNOWN; } @@ -255,6 +375,48 @@ std::string checkoutErrorMessages; int retVal = 0; + // Get update command + std::string updateCommand + = this->CTest->GetCTestConfiguration("UpdateCommand"); + if ( updateCommand.empty() ) + { + updateCommand = this->CTest->GetCTestConfiguration("CVSCommand"); + if ( updateCommand.empty() ) + { + updateCommand = this->CTest->GetCTestConfiguration("SVNCommand"); + if ( updateCommand.empty() ) + { + updateCommand = this->CTest->GetCTestConfiguration("BZRCommand"); + if ( updateCommand.empty() ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot find CVSCommand, SVNCommand, or UpdateCommand key in the " + "DartConfiguration.tcl" << std::endl); + return -1; + } + else + { + updateType = e_BZR; + } + } + else + { + updateType = e_SVN; + } + } + else + { + updateType = e_CVS; + } + } + else + { + updateType = this->DetermineType(updateCommand.c_str(), + this->CTest->GetCTestConfiguration("UpdateType").c_str()); + } + + if (updateType == e_BZR) return ProcessHandlerBZR(updateCommand); + // Get source dir const char* sourceDirectory = this->GetOption("SourceDirectory"); if ( !sourceDirectory ) @@ -329,42 +491,10 @@ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Updating the repository: " << sourceDirectory << std::endl); - // Get update command - std::string updateCommand - = this->CTest->GetCTestConfiguration("UpdateCommand"); - if ( updateCommand.empty() ) - { - updateCommand = this->CTest->GetCTestConfiguration("CVSCommand"); - if ( updateCommand.empty() ) - { - updateCommand = this->CTest->GetCTestConfiguration("SVNCommand"); - if ( updateCommand.empty() ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find CVSCommand, SVNCommand, or UpdateCommand key in the " - "DartConfiguration.tcl" << std::endl); - return -1; - } - else - { - updateType = e_SVN; - } - } - else - { - updateType = e_CVS; - } - } - else - { - updateType = this->DetermineType(updateCommand.c_str(), - this->CTest->GetCTestConfiguration("UpdateType").c_str()); - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Use " << cmCTestUpdateHandlerUpdateToString(updateType) << " repository type" - << std::endl;); - + << std::endl;); + // And update options std::string updateOptions = this->CTest->GetCTestConfiguration("UpdateOptions"); @@ -382,6 +512,9 @@ case cmCTestUpdateHandler::e_SVN: updateOptions = this->CTest->GetCTestConfiguration("SVNUpdateOptions"); break; + case cmCTestUpdateHandler::e_BZR: + updateOptions = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + break; } } @@ -410,8 +543,13 @@ break; case cmCTestUpdateHandler::e_SVN: extra_update_opts += "-r \"{" + today_update_date +" +0000}\""; + break; + case cmCTestUpdateHandler::e_BZR: + // bzr do not handle time zone specifiers + // The pulled revision will be the first AFTER the date specified + extra_update_opts += "-r date:" + today_update_date +" +0000}\""; break; - } + } } bool res = true; @@ -1113,3 +1251,726 @@ } return count; } + +int cmCTestUpdateHandler::ProcessHandlerBZR(const std::string & update_command) +{ + std::string updateCommand = update_command; + int count = 0; + std::string::size_type cc, kk; + bool updateProducedError = false; + std::string goutput; + std::string errors; + + std::string checkoutErrorMessages; + int retVal = 0; + + // 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); + } + + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "Updating the repository" << std::endl); + + const char* initialCheckoutCommand = this->GetOption("InitialCheckout"); + if ( initialCheckoutCommand ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " First perform the initial checkout: " << initialCheckoutCommand + << std::endl); + cmStdString parent = cmSystemTools::GetParentDirectory(sourceDirectory); + if ( parent.empty() ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Something went wrong when trying " + "to determine the parent directory of " << sourceDirectory + << std::endl); + return -1; + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Perform checkout in directory: " << parent.c_str() << std::endl); + if ( !cmSystemTools::MakeDirectory(parent.c_str()) ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot create parent directory: " << parent.c_str() + << " of the source directory: " << sourceDirectory << std::endl); + return -1; + } + ofs << "* Run initial checkout" << std::endl; + ofs << " Command: " << initialCheckoutCommand << std::endl; + cmCTestLog(this->CTest, DEBUG, " Before: " + << initialCheckoutCommand << std::endl); + bool retic = this->CTest->RunCommand(initialCheckoutCommand, &goutput, + &errors, &retVal, parent.c_str(), 0 /* Timeout */); + cmCTestLog(this->CTest, DEBUG, " After: " + << initialCheckoutCommand << std::endl); + ofs << " Output: " << goutput.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + if ( !retic || retVal ) + { + cmOStringStream ostr; + ostr << "Problem running initial checkout Output [" << goutput + << "] Errors [" << errors << "]"; + cmCTestLog(this->CTest, ERROR_MESSAGE, ostr.str().c_str() << std::endl); + checkoutErrorMessages += ostr.str(); + updateProducedError = true; + } + if(!this->CTest->InitializeFromCommand(this->Command)) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Fatal Error in initialize: " + << std::endl); + cmSystemTools::SetFatalErrorOccured(); + return -1; + } + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Updating the repository: " + << sourceDirectory << std::endl); + + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Use " + << cmCTestUpdateHandlerUpdateToString(e_BZR) << " repository type" + << std::endl;); + + // And update options + std::string updateOptions + = this->CTest->GetCTestConfiguration("UpdateOptions"); + if ( updateOptions.empty() ) + { + updateOptions = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + + // Get update time + time_t todayUpdateTime = -1; + if ( this->CTest->GetTestModel() == cmCTest::NIGHTLY ) + { + struct tm* t = this->CTest->GetNightlyTime( + this->CTest->GetCTestConfiguration("NightlyStartTime"), + this->CTest->GetTomorrowTag()); + todayUpdateTime = mktime(t); + } + + bool res = true; + + updateCommand = "\"" + updateCommand + "\""; + int current_revision = 0; + int latest_revision = 0; + std::string parentBranch; + + // First, retrieve current revision number + std::string command = updateCommand + " revno"; + if ( !command.empty() ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "* Get current revision: " << command.c_str() << std::endl); + if ( !this->CTest->GetShowOnly() ) + { + ofs << "* Get current revision" << std::endl; + ofs << " Command: " << command.c_str() << std::endl; + res = this->CTest->RunCommand(command.c_str(), &goutput, &errors, + &retVal, sourceDirectory, 0 /*this->TimeOut*/); + + ofs << " Output: " << goutput.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + if ( ofs ) + { + ofs << "--- Current rev ---" << std::endl; + ofs << goutput << std::endl; + } + + if ( !res || retVal ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to retrieve current bzr revision" + << std::endl); + return -1; + } + + cmsys::RegularExpression current_revision_regex( + "([0-9]+)"); + if ( current_revision_regex.find(goutput.c_str()) ) + { + std::string currentRevisionString + = current_revision_regex.match(1); + current_revision = atoi(currentRevisionString.c_str()); + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Old revision of repository is: " << current_revision + << std::endl); + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to retrieve current bzr revision" + << std::endl); + return -1; + } + + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Get current revision with command: " << command << std::endl); + } + } + + // Retrieve checkout or parent branch + command = updateCommand + " info"; + // + // Get initial repository information if that is possible. With subversion, + // this will check the current revision. + // + if ( !command.empty() ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "* Get repository information: " << command.c_str() << std::endl); + if ( !this->CTest->GetShowOnly() ) + { + ofs << "* Get repository information" << std::endl; + ofs << " Command: " << command.c_str() << std::endl; + res = this->CTest->RunCommand(command.c_str(), &goutput, &errors, + &retVal, sourceDirectory, 0 /*this->TimeOut*/); + + ofs << " Output: " << goutput.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + if ( ofs ) + { + ofs << "--- Update information ---" << std::endl; + ofs << goutput << std::endl; + } + + if ( !res || retVal ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to retrieve bzr info" + << std::endl); + return -1; + } + + cmsys::RegularExpression checkout_regex("checkout of branch: *([^\t\r\n]+)"); + cmsys::RegularExpression parent_regex("parent branch: *([^\t\r\n]+)"); + + if ( checkout_regex.find(goutput.c_str()) ) + { + parentBranch + = checkout_regex.match(1); + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Checkout of branch: " << parentBranch + << std::endl); + } + else + { + if ( parent_regex.find(goutput.c_str()) ) + { + parentBranch + = parent_regex.match(1); + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Parent branch: " << parentBranch + << std::endl); + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to retrieve parent branch" + << std::endl); + return -1; + } + } + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Get information with command: " << command << std::endl); + } + } + + + // Now retrieve log + cmOStringStream revStr; + revStr << current_revision; + + command = updateCommand + " log -r " + revStr.str() + ".. --xml " + parentBranch; + if ( !command.empty() ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "* Get repository information: " << command.c_str() << std::endl); + if ( !this->CTest->GetShowOnly() ) + { + ofs << "* Get repository information" << std::endl; + ofs << " Command: " << command.c_str() << std::endl; + res = this->CTest->RunCommand(command.c_str(), &goutput, &errors, + &retVal, sourceDirectory, 0 /*this->TimeOut*/); + + ofs << " Output: " << goutput.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + if ( ofs ) + { + ofs << "--- Update information ---" << std::endl; + ofs << goutput << std::endl; + } + + if ( !res || retVal ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to retrieve bzr log" + << std::endl); + return -1; + } + + cmCTestUpdateHandlerBZRXMLParser parser(this); + if ( parser.Parse(goutput.c_str()) ) + { + cmCTestUpdateHandlerBZRXMLParser:: + t_VectorOfCommits::iterator it; + for ( it = parser.GetCommits()->begin(); + it != parser.GetCommits()->end(); + ++ it ) + { + std::string date = it->Date; + time_t commitTime = curl_getdate(date.c_str(), 0); + if ( todayUpdateTime<0 || commitTime < todayUpdateTime ) + { + latest_revision = it->Revision; + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Latest revision: " << latest_revision + << std::endl); + break; + } + } + } + + if ( latest_revision == 0 ) + { + latest_revision = current_revision; + } + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Get log with command: " << command << std::endl); + } + } + + cmGeneratedFileStream os; + if ( !this->StartResultingXML("Update", os) ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open log file" + << std::endl); + return -1; + } + std::string start_time = this->CTest->CurrentTime(); + unsigned int start_time_time = + static_cast<unsigned int>(cmSystemTools::GetTime()); + double elapsed_time_start = cmSystemTools::GetTime(); + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "* Update repository: " + << command.c_str() << std::endl); + if ( !this->CTest->GetShowOnly() ) + { + cmOStringStream revStr; + revStr << latest_revision; + command = updateCommand + " pull -r " + revStr.str() + " " + parentBranch; + std::string partialOutput; + ofs << "* Update repository: " << std::endl; + ofs << " Command: " << command.c_str() << std::endl; + bool res1 = this->CTest->RunCommand(command.c_str(), &partialOutput, + &errors, + &retVal, sourceDirectory, 0 /*this->TimeOut*/); + ofs << " Output: " << partialOutput.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + goutput = errors + partialOutput + "\nstatus:"; + command = updateCommand + " status -SV"; + ofs << "* Status repository: " << std::endl; + ofs << " Command: " << command.c_str() << std::endl; + res = this->CTest->RunCommand(command.c_str(), &partialOutput, + &errors, &retVal, sourceDirectory, 0 /*this->TimeOut*/); + ofs << " Output: " << partialOutput.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + goutput += errors + partialOutput; + res = res && res1; + ofs << " Total output of update: " << goutput.c_str() << std::endl; + + if ( ofs ) + { + ofs << "--- Update repository ---" << std::endl; + ofs << goutput << std::endl; + } + } + if ( !res || retVal ) + { + updateProducedError = true; + checkoutErrorMessages += " " + goutput; + } + + os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<Update mode=\"Client\" Generator=\"ctest-" + << cmVersion::GetCMakeVersion() << "\">\n" + << "\t<Site>" << this->CTest->GetCTestConfiguration("Site") << "</Site>\n" + << "\t<BuildName>" << this->CTest->GetCTestConfiguration("BuildName") + << "</BuildName>\n" + << "\t<BuildStamp>" << this->CTest->GetCurrentTag() << "-" + << this->CTest->GetTestModelString() << "</BuildStamp>" << std::endl; + os << "\t<StartDateTime>" << start_time << "</StartDateTime>\n" + << "\t<StartTime>" << start_time_time << "</StartTime>\n" + << "\t<UpdateCommand>" << this->CTest->MakeXMLSafe(command) + << "</UpdateCommand>\n" + << "\t<UpdateType>" << this->CTest->MakeXMLSafe( + cmCTestUpdateHandlerUpdateToString(e_BZR)) + << "</UpdateType>\n"; + + // Even though it failed, we may have some useful information. Try to + // continue... + std::vector<cmStdString> lines; + cmSystemTools::Split(goutput.c_str(), lines); + std::vector<cmStdString> errLines; + cmSystemTools::Split(errors.c_str(), errLines); + lines.insert(lines.end(), errLines.begin(), errLines.end()); + + cmsys::RegularExpression file_update_line("([A-Z+-?][A-Z]| [A-Z]|[A-Z+-?] ) *([^\t\r\n]*)"); + cmsys::RegularExpression status_line("status:"); + cmsys::RegularExpression email_regex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]+)>"); + + bool processingStatusOutput = false; + + std::string current_path = "<no-path>"; + bool first_file = true; + + cmCTestUpdateHandler::AuthorsToUpdatesMap authors_files_map; + int numUpdated = 0; + int numModified = 0; + int numConflicting = 0; + + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (each . represents one updated file):" + << std::endl); + int file_count = 0; + std::string removed_line; + for ( cc= 0; cc < lines.size(); cc ++ ) + { + const char* line = lines[cc].c_str(); + if ( status_line.find(line) ) + { + processingStatusOutput = true; + continue; + } + + if ( file_update_line.find(line) ) + { + if ( file_count == 0 ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << std::flush); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + std::string upChar = file_update_line.match(1); + std::string upFile = file_update_line.match(2); + char mod1 = upChar[0]; + char mod2 = upChar[1]; + bool modifiedOrConflict = false; + if ( mod1 != 'P' && mod2!= 'K' && mod1 != '?' ) + { + count ++; + modifiedOrConflict = true; + } + const char* file = upFile.c_str(); + cmCTestLog(this->CTest, DEBUG, "Line" << cc << ": " << mod1 << mod2 << " - " + << file << std::endl); + + std::string output; + if ( modifiedOrConflict ) + { + std::string logcommand; + cmOStringStream logCommandStream; + logCommandStream << updateCommand << " log -r " + << current_revision << ".." << latest_revision + << " --xml \"" << file << "\""; + logcommand = logCommandStream.str(); + + cmCTestLog(this->CTest, DEBUG, "Do log: " << logcommand << std::endl); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "* Get file update information: " << logcommand.c_str() + << std::endl); + ofs << "* Get log information for file: " << file << std::endl; + ofs << " Command: " << logcommand.c_str() << std::endl; + res = this->CTest->RunCommand(logcommand.c_str(), &output, &errors, + &retVal, sourceDirectory, 0 ); + ofs << " Output: " << output.c_str() << std::endl; + ofs << " Errors: " << errors.c_str() << std::endl; + if ( ofs ) + { + ofs << output << std::endl; + } + } + + cmOStringStream revStream; + revStream << latest_revision; + std::string srevision1 = revStream.str(); + std::string sdate1 = "Unknown"; + std::string sauthor1 = "Unknown"; + std::string semail1 = "Unknown"; + std::string comment1 = ""; + revStream.seekp(0); + revStream << current_revision; + std::string srevision2 = revStream.str(); + std::string sdate2 = "Unknown"; + std::string sauthor2 = "Unknown"; + std::string comment2 = ""; + std::string semail2 = "Unknown"; + + if ( res ) + { + cmCTestLog(this->CTest, DEBUG, output << std::endl); + cmCTestUpdateHandlerBZRXMLParser parser(this); + if ( parser.Parse(output.c_str()) ) + { + int minrev = parser.GetMinRevision(); + int maxrev = parser.GetMaxRevision(); + cmCTestUpdateHandlerBZRXMLParser:: + t_VectorOfCommits::iterator it; + for ( it = parser.GetCommits()->begin(); + it != parser.GetCommits()->end(); + ++ it ) + { + if ( it->Revision == maxrev ) + { + cmOStringStream mRevStream; + mRevStream << maxrev; + srevision1 = mRevStream.str(); + sauthor1 = it->Author; + comment1 = it->Message; + sdate1 = it->Date; + } + else if ( it->Revision == minrev ) + { + cmOStringStream mRevStream; + mRevStream << minrev; + srevision2 = mRevStream.str(); + sauthor2 = it->Author; + comment2 = it->Message; + sdate2 = it->Date; + } + } + } + } + + if ( email_regex.find(sauthor1) ) + { + sauthor1 = email_regex.match(1); + semail1 = email_regex.match(2); + } + if ( email_regex.find(sauthor2) ) + { + sauthor2 = email_regex.match(1); + semail2 = email_regex.match(2); + } + + if ( mod2 == 'M' && processingStatusOutput) + { + comment1 = "Locally modified file\n"; + sauthor1 = "Local User"; + } + if ( mod2 == 'D' ) + { + comment1 += " - Removed file\n"; + } + if ( mod1 == 'C' ) + { + comment1 = "Conflict while updating\n"; + sauthor1 = "Local User"; + } + std::string path = cmSystemTools::GetFilenamePath(file); + std::string fname = cmSystemTools::GetFilenameName(file); + if ( path != current_path ) + { + if ( !first_file ) + { + os << "\t</Directory>" << std::endl; + } + else + { + first_file = false; + } + os << "\t<Directory>\n" + << "\t\t<Name>" << path << "</Name>" << std::endl; + } + if ( mod1 == 'C' ) + { + numConflicting ++; + os << "\t<Conflicting>" << std::endl; + } + else if ( mod2 == 'M' && processingStatusOutput) + { + numModified ++; + os << "\t<Modified>" << std::endl; + } + else + { + numUpdated ++; + os << "\t<Updated>" << std::endl; + } + if ( srevision2 == "Unknown" ) + { + srevision2 = srevision1; + } + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "File: " + << path.c_str() << " / " << fname.c_str() << " was updated by " + << sauthor1.c_str() << " to revision: " << srevision1.c_str() + << " from revision: " << srevision2.c_str() << std::endl); + os << "\t\t<File Directory=\"" << cmCTest::MakeXMLSafe(path) << "\">" + << cmCTest::MakeXMLSafe(fname) + << "</File>\n" + << "\t\t<Directory>" << cmCTest::MakeXMLSafe(path) + << "</Directory>\n" + << "\t\t<FullName>" << cmCTest::MakeXMLSafe(file) << "</FullName>\n" + << "\t\t<CheckinDate>" << cmCTest::MakeXMLSafe(sdate1) + << "</CheckinDate>\n" + << "\t\t<Author>" << cmCTest::MakeXMLSafe(sauthor1) << "</Author>\n" + << "\t\t<Email>" << cmCTest::MakeXMLSafe(semail1) << "</Email>\n" + << "\t\t<Log>" << cmCTest::MakeXMLSafe(comment1) << "</Log>\n" + << "\t\t<Revision>" << srevision1 << "</Revision>\n" + << "\t\t<PriorRevision>" << srevision2 << "</PriorRevision>" + << std::endl; + if ( srevision2 != srevision1 ) + { + os + << "\t\t<Revisions>\n" + << "\t\t\t<Revision>" << srevision1 << "</Revision>\n" + << "\t\t\t<PreviousRevision>" << srevision2 + << "</PreviousRevision>\n" + << "\t\t\t<Author>" << cmCTest::MakeXMLSafe(sauthor1) + << "</Author>\n" + << "\t\t\t<Date>" << cmCTest::MakeXMLSafe(sdate1) + << "</Date>\n" + << "\t\t\t<Comment>" << cmCTest::MakeXMLSafe(comment1) + << "</Comment>\n" + << "\t\t\t<Email>" << cmCTest::MakeXMLSafe(semail1) + << "</Email>\n" + << "\t\t</Revisions>\n" + << "\t\t<Revisions>\n" + << "\t\t\t<Revision>" << srevision2 << "</Revision>\n" + << "\t\t\t<PreviousRevision>" << srevision2 + << "</PreviousRevision>\n" + << "\t\t\t<Author>" << cmCTest::MakeXMLSafe(sauthor2) + << "</Author>\n" + << "\t\t\t<Date>" << cmCTest::MakeXMLSafe(sdate2) + << "</Date>\n" + << "\t\t\t<Comment>" << cmCTest::MakeXMLSafe(comment2) + << "</Comment>\n" + << "\t\t\t<Email>" << cmCTest::MakeXMLSafe(semail2) + << "</Email>\n" + << "\t\t</Revisions>" << std::endl; + } + if ( mod1 == 'C' ) + { + os << "\t</Conflicting>" << std::endl; + } + else if ( mod2 == 'M' && processingStatusOutput) + { + os << "\t</Modified>" << std::endl; + } + else + { + os << "\t</Updated>" << std::endl; + } + cmCTestUpdateHandler::UpdateFiles *u = &authors_files_map[sauthor1]; + cmCTestUpdateHandler::StringPair p; + p.first = path; + p.second = fname; + u->push_back(p); + + current_path = path; + file_count ++; + } + } + if ( file_count ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + } + if ( numUpdated ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numUpdated + << " updated files" << std::endl); + } + if ( numModified ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numModified + << " locally modified files" + << std::endl); + } + if ( numConflicting ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Found " << numConflicting + << " conflicting files" + << std::endl); + } + if ( numModified == 0 && numConflicting == 0 && numUpdated == 0 ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Project is up-to-date" + << std::endl); + } + if ( !first_file ) + { + os << "\t</Directory>" << std::endl; + } + + cmCTestUpdateHandler::AuthorsToUpdatesMap::iterator it; + for ( it = authors_files_map.begin(); + it != authors_files_map.end(); + it ++ ) + { + os << "\t<Author>\n" + << "\t\t<Name>" << cmCTest::MakeXMLSafe(it->first) << "</Name>" << std::endl; + cmCTestUpdateHandler::UpdateFiles *u = &(it->second); + for ( cc = 0; cc < u->size(); cc ++ ) + { + os << "\t\t<File Directory=\"" << (*u)[cc].first << "\">" + << cmCTest::MakeXMLSafe((*u)[cc].second) << "</File>" << std::endl; + } + os << "\t</Author>" << std::endl; + } + + cmCTestLog(this->CTest, DEBUG, "End" << std::endl); + std::string end_time = this->CTest->CurrentTime(); + os << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" + << "\t<EndTime>" << static_cast<unsigned int>(cmSystemTools::GetTime()) + << "</EndTime>\n" + << "<ElapsedMinutes>" << + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 + << "</ElapsedMinutes>\n" + << "\t<UpdateReturnStatus>"; + if ( numModified > 0 || numConflicting > 0 ) + { + os << "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 ( updateProducedError ) + { + os << "Update error: "; + os << this->CTest->MakeXMLSafe(checkoutErrorMessages); + cmCTestLog(this->CTest, ERROR_MESSAGE, " Update with command: " + << command << " failed" << std::endl); + } + os << "</UpdateReturnStatus>" << std::endl; + os << "</Update>" << std::endl; + + if (! res ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error(s) when updating the project" << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, "Output: " + << goutput << std::endl); + return -1; + } + return count; +} diff -urN cmake-2.6.1/Source/CTest/cmCTestUpdateHandler.h cmake-2.6.1-patched/Source/CTest/cmCTestUpdateHandler.h --- cmake-2.6.1/Source/CTest/cmCTestUpdateHandler.h 2008-08-01 17:34:54.000000000 +0200 +++ cmake-2.6.1-patched/Source/CTest/cmCTestUpdateHandler.h 2008-08-20 20:59:48.000000000 +0200 @@ -26,6 +26,8 @@ # pragma set woff 1375 /* base class destructor not virtual */ #endif +class cmGeneratedFileStream; + /** \class cmCTestUpdateHandler * \brief A class that handles ctest -S invocations * @@ -46,6 +48,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_LAST }; @@ -64,6 +67,7 @@ // Determine the type of version control int DetermineType(const char* cmd, const char* type); + int ProcessHandlerBZR(const std::string & updateCommand); }; #if defined(__sgi) && !defined(__GNUC__) ![]() Index: Modules/CTest.cmake =================================================================== RCS file: /cvsroot/CMake/CMake/Modules/CTest.cmake,v retrieving revision 1.14 diff -u -r1.14 CTest.cmake --- Modules/CTest.cmake 11 Feb 2009 20:18:13 -0000 1.14 +++ Modules/CTest.cmake 15 Apr 2009 10:43:37 -0000 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -129,6 +134,11 @@ IF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") + IF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") ENDIF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") ENDIF(UPDATE_TYPE MATCHES "[Cc][Vv][Ss]") @@ -206,8 +216,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE Index: Source/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CMakeLists.txt,v retrieving revision 1.413 diff -u -r1.413 CMakeLists.txt --- Source/CMakeLists.txt 16 Mar 2009 14:40:18 -0000 1.413 +++ Source/CMakeLists.txt 15 Apr 2009 10:43:37 -0000 @@ -359,6 +359,8 @@ CTest/cmCTestCVS.h CTest/cmCTestSVN.cxx CTest/cmCTestSVN.h + CTest/cmCTestBZR.cxx + CTest/cmCTestBZR.h ) # Build CTestLib Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: Source/CTest/cmCTestBZR.cxx diff -N Source/CTest/cmCTestBZR.cxx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.cxx 15 Apr 2009 10:43:38 -0000 @@ -0,0 +1,989 @@ +#include "cmCTestBZR.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + +#include <cm_expat.h> + +//---------------------------------------------------------------------------- +extern "C" +{ +int cmBZRXMLParserUnknownEncodingHandler(void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info) +{ + static const int latin1[]= + { + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0005, + 0x0006, + 0x0007, + 0x0008, + 0x0009, + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x000E, + 0x000F, + 0x0010, + 0x0011, + 0x0012, + 0x0013, + 0x0014, + 0x0015, + 0x0016, + 0x0017, + 0x0018, + 0x0019, + 0x001A, + 0x001B, + 0x001C, + 0x001D, + 0x001E, + 0x001F, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x20AC, + 0x0081, + 0x201A, + 0x0192, + 0x201E, + 0x2026, + 0x2020, + 0x2021, + 0x02C6, + 0x2030, + 0x0160, + 0x2039, + 0x0152, + 0x008D, + 0x017D, + 0x008F, + 0x0090, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, + 0x2013, + 0x2014, + 0x02DC, + 0x2122, + 0x0161, + 0x203A, + 0x0153, + 0x009D, + 0x017E, + 0x0178, + 0x00A0, + 0x00A1, + 0x00A2, + 0x00A3, + 0x00A4, + 0x00A5, + 0x00A6, + 0x00A7, + 0x00A8, + 0x00A9, + 0x00AA, + 0x00AB, + 0x00AC, + 0x00AD, + 0x00AE, + 0x00AF, + 0x00B0, + 0x00B1, + 0x00B2, + 0x00B3, + 0x00B4, + 0x00B5, + 0x00B6, + 0x00B7, + 0x00B8, + 0x00B9, + 0x00BA, + 0x00BB, + 0x00BC, + 0x00BD, + 0x00BE, + 0x00BF, + 0x00C0, + 0x00C1, + 0x00C2, + 0x00C3, + 0x00C4, + 0x00C5, + 0x00C6, + 0x00C7, + 0x00C8, + 0x00C9, + 0x00CA, + 0x00CB, + 0x00CC, + 0x00CD, + 0x00CE, + 0x00CF, + 0x00D0, + 0x00D1, + 0x00D2, + 0x00D3, + 0x00D4, + 0x00D5, + 0x00D6, + 0x00D7, + 0x00D8, + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x00DD, + 0x00DE, + 0x00DF, + 0x00E0, + 0x00E1, + 0x00E2, + 0x00E3, + 0x00E4, + 0x00E5, + 0x00E6, + 0x00E7, + 0x00E8, + 0x00E9, + 0x00EA, + 0x00EB, + 0x00EC, + 0x00ED, + 0x00EE, + 0x00EF, + 0x00F0, + 0x00F1, + 0x00F2, + 0x00F3, + 0x00F4, + 0x00F5, + 0x00F6, + 0x00F7, + 0x00F8, + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x00FD, + 0x00FE, + 0x00FF + }; + + if ( name==std::string("ascii") || name==std::string("cp1252") ) + { + for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; + return 1; + } + + return 0; +} +} + +//---------------------------------------------------------------------------- +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log) +{ + this->PriorRev = this->Unknown; + // Even though it is specified in the documention, with bzr 1.13 + // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. + // Since it doesn't hurt, we specify this environment variable. + cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); +} + +//---------------------------------------------------------------------------- +cmCTestBZR::~cmCTestBZR() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +{ +public: + InfoParser(cmCTestBZR* bzr, const char* prefix): + BZR(bzr), CheckOutFound(false) + { + this->SetLog(&bzr->Log, prefix); + this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); + this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); + } +private: + cmCTestBZR* BZR; + bool CheckOutFound; + cmsys::RegularExpression RegexCheckOut; + cmsys::RegularExpression RegexParent; + virtual bool ProcessLine() + { + if(this->RegexCheckOut.find(this->Line)) + { + this->BZR->URL = this->RegexCheckOut.match(1); + CheckOutFound = true; + } + else if(!CheckOutFound && this->RegexParent.find(this->Line)) + { + this->BZR->URL = this->RegexParent.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +{ +public: + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): + BZR(bzr), Rev(rev) + { + this->SetLog(&bzr->Log, prefix); + this->RegexRevno.compile("^([0-9]+)$"); + } +private: + cmCTestBZR* BZR; + std::string& Rev; + cmsys::RegularExpression RegexRevno; + virtual bool ProcessLine() + { + if(this->RegexRevno.find(this->Line)) + { + this->Rev = this->RegexRevno.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::LoadInfo() +{ + // Run "bzr info" to get the repository info from the work tree. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_info[] = {bzr, "info", 0}; + InfoParser iout(this, "info-out> "); + OutputLogger ierr(this->Log, "info-err> "); + this->RunChild(bzr_info, &iout, &ierr); + + // Run "bzr revno" to get the repository revision number from the work tree. + const char* bzr_revno[] = {bzr, "revno", 0}; + std::string rev; + RevnoParser rout(this, "revno-out> ", rev); + OutputLogger rerr(this->Log, "revno-err> "); + this->RunChild(bzr_revno, &rout, &rerr); + + return rev; +} + +void cmCTestBZR::NoteOldRevision() +{ + this->OldRevision = this->LoadInfo(); + this->Log << "Revision before update: " << this->OldRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::NoteNewRevision() +{ + this->NewRevision = this->LoadInfo(); + this->Log << "Revision after update: " << this->NewRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + this->Log << "URL = " << this->URL << "\n"; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), EmailRegex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } +private: + cmCTestBZR* BZR; + + typedef cmCTestBZR::Revision Revision; + Revision Rev; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + this->Rev = Revision(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + this->BZR->DoRevision(this->Rev); + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexUpdate; + + virtual bool ProcessChunk(const char* first, int length) + { + bool last_is_new_line = (*first == '\r' || *first == '\n'); + + const char* const last = first + length; + for(const char* c = first; c != last; ++c) + { + if(*c == '\r' || *c == '\n') + { + if(!last_is_new_line) + { + // Log this line. + if(this->Log && this->Prefix) + { + *this->Log << this->Prefix << this->Line << "\n"; + } + + // Hand this line to the subclass implementation. + if(!this->ProcessLine()) + { + this->Line = ""; + return false; + } + + this->Line = ""; + last_is_new_line = true; + } + } + else + { + // Append this character to the line under construction. + this->Line.append(1, *c); + last_is_new_line = false; + } + } + return true; + } + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)[0], + this->RegexUpdate.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + const std::string dir = cmSystemTools::GetFilenamePath(path); + const std::string name = cmSystemTools::GetFilenameName(path); + + if ( c0=='C' ) + { + this->BZR->Dirs[dir][name].Status = PathConflicting; + return; + } + + if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) + { + this->BZR->Dirs[dir][name].Status = PathUpdated; + return; + } + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::GetNightlyOrLatestRevno() +{ + // Parse "bzr log -r curr_rev.. --xml bzr_url" to get a list of all revision numbers + std::string revs = this->OldRevision + ".."; + + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log4revno-out> "); + OutputLogger err(this->Log, "log4revno-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + + // In case no revision occured we keep the old revision + if(this->Revisions.empty()) return this->OldRevision; + + // Get time for nightly build (even though we don't always use it) + // This is in UTC time + struct tm* t = this->CTest->GetNightlyTime( + this->CTest->GetCTestConfiguration("NightlyStartTime"), + this->CTest->GetTomorrowTag()); + + if (t->tm_isdst != 0) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Daylight saving time is used in the result of GetNightlyTime which is supposed to use UTC time. Results might not be quite accurate." + << std::endl); + } + + // To be consistent with UTC time we should not use mktime on UTC-defined + // tm structs. It is OK to do so here since we only need to do some + // ordering comparisons on timestamps. + // A better solution would be to use timegm instead of mktime but + // timegm is not a widely supported function + const time_t nightly_timestamp = mktime(t); + + const bool is_nightly = this->CTest->GetTestModel() == cmCTest::NIGHTLY; + + // If we don't find anything, by default the new revision is the old one + std::string new_revision = this->OldRevision; + + for(std::list<Revision>::const_iterator ri = this->Revisions.begin(); + ri != this->Revisions.end(); ++ri) + { + if (!is_nightly) + { + if(atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + { + new_revision = ri->Rev; + } + } + else + { + // Parse the time of this revision + // curl_getdate does not parse "Tue 2009-04-14 15:36:32 +0200" + // strptime is not available on msvc and it is not clear to me how it handles timezones + // This means we need a workaround + const std::string & date = ri->Date; + struct tm timeinfo; + + cmsys::RegularExpression regex_date( + ".* ([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) ([-+])([0-9][0-9])([0-9][0-9])"); + if (!regex_date.find(date)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to parse bzr commit time: " << date.c_str() + << std::endl); + continue; + } + + // Construct a UTC tm + timeinfo.tm_year = atoi(regex_date.match(1).c_str()) - 1900; + timeinfo.tm_mon = atoi(regex_date.match(2).c_str()) - 1; + timeinfo.tm_mday = atoi(regex_date.match(3).c_str()); + timeinfo.tm_hour = atoi(regex_date.match(4).c_str()); + timeinfo.tm_min = atoi(regex_date.match(5).c_str()); + timeinfo.tm_sec = atoi(regex_date.match(6).c_str()); + + timeinfo.tm_isdst = 0; + // timeinfo.tm_wday; // Should not be used + // timeinfo.tm_yday; // Should not be used + + time_t offset = atoi(regex_date.match(8).c_str())*3600 + + atoi(regex_date.match(9).c_str())*60; + if (regex_date.match(7)=="-") offset = - offset; + + // See previous comment. A nicer solution would be to use timegm + // instead of mktime but this should not impac the results. + const time_t commit_timestamp = mktime(&timeinfo)-offset; + + // Keep it if it is the highest revision number prior to the nightly time + if((atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + && (commit_timestamp <= nightly_timestamp)) + { + new_revision = ri->Rev; + } + } + } + + return new_revision; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + + // Get the revision number used to update the repository + std::string new_revision = this->GetNightlyOrLatestRevno(); + + std::vector<char const*> bzr_update; + bzr_update.push_back(this->CommandLineTool.c_str()); + bzr_update.push_back("pull"); + bzr_update.push_back("-r"); + bzr_update.push_back(new_revision.c_str()); + bzr_update.push_back(this->URL.c_str()); + + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + bzr_update.push_back(ai->c_str()); + } + bzr_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + // For some reason bzr uses cerr to display the update status + return this->RunUpdateCommand(&bzr_update[0], &err, &out); + return false; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::SingleFileLogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + SingleFileLogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), RevPtr(0), EmailRegex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~SingleFileLogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } + + typedef cmCTestBZR::Revision Revision; + Revision * GetListedRev(){return this->RevPtr;} +private: + cmCTestBZR* BZR; + + Revision * RevPtr; + Revision Rev; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + RevPtr = 0; + this->Rev = Revision(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + std::list<Revision>::iterator + it = std::find(this->BZR->Revisions.begin(), this->BZR->Revisions.end(), this->Rev); + if(it!=this->BZR->Revisions.end()) this->RevPtr = &(*it); + else this->RevPtr = 0; + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::DoRevision(Revision const& revision) +{ + // Indicate we found a revision. + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + + // Ignore changes in the old revision. + if(revision.Rev == this->OldRevision) + { + this->PriorRev = revision; + return; + } + + // Store the revision. + this->Revisions.push_back(revision); + + // Report this revision. + Revision const& rev = this->Revisions.back(); + this->Log << "Found revision " << rev.Rev << "\n" + << " author = " << rev.Author << "\n" + << " date = " << rev.Date << "\n"; + + + // Update information about revisions of the changed files. + for(std::map<cmStdString, Directory>::iterator + di = this->Dirs.begin(); di != this->Dirs.end(); ++di) + { + const std::string & dir = di->first; + for(std::map<cmStdString, File>::iterator + fi = (di->second).begin(); fi != (di->second).end(); ++fi) + { + const std::string & name = fi->first; + const std::string path = dir + (dir.empty()?"":"/") + name; + + // Run "bzr log" to get the revision of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revision.Rev.c_str(), "--xml", path.c_str(), 0}; + SingleFileLogParser out(this, "sf-log-out> "); + OutputLogger err(this->Log, "sf-log-err> "); + this->RunChild(bzr_log, &out, &err); + + Revision * rev = out.GetListedRev(); + + if( rev ) + { + File& file = fi->second; + + if (!file.Rev) + { + file.PriorRev = &this->PriorRev; + file.Rev = rev; + } + else if ( atoi(rev->Rev.c_str()) > atoi(file.Rev->Rev.c_str()) ) + { + file.PriorRev = file.Rev; + file.Rev = rev; + } + } + } + } +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + this->Revisions.clear(); + std::string revs; + if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) + { + // DoRevision takes care of discarding the information about OldRevision + revs = this->OldRevision + ".." + this->NewRevision; + } + else + { + return; + } + + // Run "bzr log" to get all global revisions of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)[0], + this->RegexStatus.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + if ( c0=='C' ) + { + this->DoPath(PathConflicting, path); + return; + } + + if ( c0 == '+' || c0 == 'R' || c0 == 'P' + || c1=='M' || c1=='K' || c1=='N' || c1=='D' + || c2 =='*' ) + { + this->DoPath(PathModified, path); + return; + } + } + + void DoPath(PathStatus status, std::string const& path) + { + std::string dir = cmSystemTools::GetFilenamePath(path); + std::string name = cmSystemTools::GetFilenameName(path); + File& file = this->BZR->Dirs[dir][name]; + file.Status = status; + // For local modifications the current rev is unknown and the + // prior rev is the latest from bzr. + if(!file.Rev && !file.PriorRev) + { + file.PriorRev = &this->BZR->PriorRev; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadModifications() +{ + // Run "bzr status" which reports local modifications. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_status[] = {bzr, "status", "-SV", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(bzr_status, &out, &err); +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::WriteXMLDirectory(std::ostream& xml, + std::string const& path, + Directory const& dir) +{ + const char* slash = path.empty()? "":"/"; + xml << "\t<Directory>\n" + << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\n"; + for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) + { + std::string full = path + slash + fi->first; + this->WriteXMLEntry(xml, path, fi->first, full, fi->second); + } + xml << "\t</Directory>\n"; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::WriteXMLUpdates(std::ostream& xml) +{ + this->LoadRevisions(); + this->LoadModifications(); + + for(std::map<cmStdString, Directory>::const_iterator + di = this->Dirs.begin(); di != this->Dirs.end(); ++di) + { + this->WriteXMLDirectory(xml, di->first, di->second); + } + + return true; +} Index: Source/CTest/cmCTestBZR.h =================================================================== RCS file: Source/CTest/cmCTestBZR.h diff -N Source/CTest/cmCTestBZR.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.h 15 Apr 2009 10:43:38 -0000 @@ -0,0 +1,66 @@ +#ifndef cmCTestBZR_h +#define cmCTestBZR_h + +#include "cmCTestVC.h" + +/** \class cmCTestBZR + * \brief Interaction with bzr command-line tool + * + */ +class cmCTestBZR: public cmCTestVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestBZR(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestBZR(); + +private: + // Implement cmCTestVC internal API. + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual std::string GetNightlyOrLatestRevno(); + virtual bool UpdateImpl(); + virtual bool WriteXMLUpdates(std::ostream& xml); + + // Update status for files in each directory. + class Directory: public std::map<cmStdString, File> {}; + std::map<cmStdString, Directory> Dirs; + + // Old and new repository revisions. + std::string OldRevision; + std::string NewRevision; + + // URL of repository directory checked out in the working tree. + std::string URL; + + // Information known about old revision. + Revision PriorRev; + + // Information about revisions from a svn log. + std::list<Revision> Revisions; + + std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + void DoRevision(Revision const& revision); + void WriteXMLDirectory(std::ostream& xml, std::string const& path, + Directory const& dir); + + // Parsing helper classes. + class InfoParser; + class RevnoParser; + class LogParser; + class UpdateParser; + class StatusParser; + class SingleFileLogParser; + friend class InfoParser; + friend class RevnoParser; + friend class LogParser; + friend class UpdateParser; + friend class StatusParser; + friend class SingleFileLogParser; +}; + +#endif Index: Source/CTest/cmCTestUpdateCommand.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v retrieving revision 1.15 diff -u -r1.15 cmCTestUpdateCommand.cxx --- Source/CTest/cmCTestUpdateCommand.cxx 11 Jul 2006 19:58:07 -0000 1.15 +++ Source/CTest/cmCTestUpdateCommand.cxx 15 Apr 2009 10:43:38 -0000 @@ -48,6 +48,10 @@ "SVNCommand", "CTEST_SVN_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); const char* initialCheckoutCommand = this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND"); Index: Source/CTest/cmCTestUpdateHandler.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v retrieving revision 1.64 diff -u -r1.64 cmCTestUpdateHandler.cxx --- Source/CTest/cmCTestUpdateHandler.cxx 20 Mar 2009 18:19:56 -0000 1.64 +++ Source/CTest/cmCTestUpdateHandler.cxx 15 Apr 2009 10:43:38 -0000 @@ -30,6 +30,7 @@ #include "cmCTestVC.h" #include "cmCTestCVS.h" #include "cmCTestSVN.h" +#include "cmCTestBZR.h" #include <cmsys/auto_ptr.hxx> @@ -50,7 +51,8 @@ { "Unknown", "CVS", - "SVN" + "SVN", + "BZR" }; static const char* cmCTestUpdateHandlerUpdateToString(int type) @@ -133,6 +135,10 @@ { return cmCTestUpdateHandler::e_SVN; } + if ( stype.find("bzr") != std::string::npos ) + { + return cmCTestUpdateHandler::e_BZR; + } } else { @@ -147,6 +153,10 @@ { return cmCTestUpdateHandler::e_SVN; } + if ( stype.find("bzr") != std::string::npos ) + { + return cmCTestUpdateHandler::e_BZR; + } } return cmCTestUpdateHandler::e_UNKNOWN; } @@ -204,6 +214,7 @@ { case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; + case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } vc->SetCommandLineTool(this->UpdateCommand); @@ -337,6 +348,12 @@ { return cmCTestUpdateHandler::e_CVS; } + sourceDirectory = dir; + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } return cmCTestUpdateHandler::e_UNKNOWN; } @@ -364,6 +381,7 @@ { case e_CVS: key = "CVSCommand"; break; case e_SVN: key = "SVNCommand"; break; + case e_BZR: key = "BZRCommand"; break; default: break; } if (key) Index: Source/CTest/cmCTestUpdateHandler.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v retrieving revision 1.11 diff -u -r1.11 cmCTestUpdateHandler.h --- Source/CTest/cmCTestUpdateHandler.h 24 Feb 2009 14:09:43 -0000 1.11 +++ Source/CTest/cmCTestUpdateHandler.h 15 Apr 2009 10:43:38 -0000 @@ -46,6 +46,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_LAST }; Index: Source/CTest/cmCTestVC.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestVC.h,v retrieving revision 1.8 diff -u -r1.8 cmCTestVC.h --- Source/CTest/cmCTestVC.h 26 Feb 2009 14:22:32 -0000 1.8 +++ Source/CTest/cmCTestVC.h 15 Apr 2009 10:43:38 -0000 @@ -79,6 +79,15 @@ std::string Date; std::string Author; std::string Log; + + bool operator==(const Revision& other) + { + if (this->Rev!=other.Rev) return false; + if (this->Date!=other.Date) return false; + if (this->Author!=other.Author) return false; + if (this->Log!=other.Log) return false; + return true; + } }; struct File; ![]() Index: Source/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CMakeLists.txt,v retrieving revision 1.415 diff -u -r1.415 CMakeLists.txt --- Source/CMakeLists.txt 22 Apr 2009 13:18:57 -0000 1.415 +++ Source/CMakeLists.txt 22 Apr 2009 15:39:38 -0000 @@ -361,6 +361,8 @@ CTest/cmCTestCVS.h CTest/cmCTestSVN.cxx CTest/cmCTestSVN.h + CTest/cmCTestBZR.cxx + CTest/cmCTestBZR.h CTest/cmCTestGIT.cxx CTest/cmCTestGIT.h ) Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: Source/CTest/cmCTestBZR.cxx diff -N Source/CTest/cmCTestBZR.cxx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.cxx 22 Apr 2009 15:39:39 -0000 @@ -0,0 +1,989 @@ +#include "cmCTestBZR.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + +#include <cm_expat.h> + +//---------------------------------------------------------------------------- +extern "C" +{ +int cmBZRXMLParserUnknownEncodingHandler(void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info) +{ + static const int latin1[]= + { + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0005, + 0x0006, + 0x0007, + 0x0008, + 0x0009, + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x000E, + 0x000F, + 0x0010, + 0x0011, + 0x0012, + 0x0013, + 0x0014, + 0x0015, + 0x0016, + 0x0017, + 0x0018, + 0x0019, + 0x001A, + 0x001B, + 0x001C, + 0x001D, + 0x001E, + 0x001F, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x20AC, + 0x0081, + 0x201A, + 0x0192, + 0x201E, + 0x2026, + 0x2020, + 0x2021, + 0x02C6, + 0x2030, + 0x0160, + 0x2039, + 0x0152, + 0x008D, + 0x017D, + 0x008F, + 0x0090, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, + 0x2013, + 0x2014, + 0x02DC, + 0x2122, + 0x0161, + 0x203A, + 0x0153, + 0x009D, + 0x017E, + 0x0178, + 0x00A0, + 0x00A1, + 0x00A2, + 0x00A3, + 0x00A4, + 0x00A5, + 0x00A6, + 0x00A7, + 0x00A8, + 0x00A9, + 0x00AA, + 0x00AB, + 0x00AC, + 0x00AD, + 0x00AE, + 0x00AF, + 0x00B0, + 0x00B1, + 0x00B2, + 0x00B3, + 0x00B4, + 0x00B5, + 0x00B6, + 0x00B7, + 0x00B8, + 0x00B9, + 0x00BA, + 0x00BB, + 0x00BC, + 0x00BD, + 0x00BE, + 0x00BF, + 0x00C0, + 0x00C1, + 0x00C2, + 0x00C3, + 0x00C4, + 0x00C5, + 0x00C6, + 0x00C7, + 0x00C8, + 0x00C9, + 0x00CA, + 0x00CB, + 0x00CC, + 0x00CD, + 0x00CE, + 0x00CF, + 0x00D0, + 0x00D1, + 0x00D2, + 0x00D3, + 0x00D4, + 0x00D5, + 0x00D6, + 0x00D7, + 0x00D8, + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x00DD, + 0x00DE, + 0x00DF, + 0x00E0, + 0x00E1, + 0x00E2, + 0x00E3, + 0x00E4, + 0x00E5, + 0x00E6, + 0x00E7, + 0x00E8, + 0x00E9, + 0x00EA, + 0x00EB, + 0x00EC, + 0x00ED, + 0x00EE, + 0x00EF, + 0x00F0, + 0x00F1, + 0x00F2, + 0x00F3, + 0x00F4, + 0x00F5, + 0x00F6, + 0x00F7, + 0x00F8, + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x00FD, + 0x00FE, + 0x00FF + }; + + if ( name==std::string("ascii") || name==std::string("cp1252") ) + { + for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; + return 1; + } + + return 0; +} +} + +//---------------------------------------------------------------------------- +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log) +{ + this->PriorRev = this->Unknown; + // Even though it is specified in the documention, with bzr 1.13 + // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. + // Since it doesn't hurt, we specify this environment variable. + cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); +} + +//---------------------------------------------------------------------------- +cmCTestBZR::~cmCTestBZR() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +{ +public: + InfoParser(cmCTestBZR* bzr, const char* prefix): + BZR(bzr), CheckOutFound(false) + { + this->SetLog(&bzr->Log, prefix); + this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); + this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); + } +private: + cmCTestBZR* BZR; + bool CheckOutFound; + cmsys::RegularExpression RegexCheckOut; + cmsys::RegularExpression RegexParent; + virtual bool ProcessLine() + { + if(this->RegexCheckOut.find(this->Line)) + { + this->BZR->URL = this->RegexCheckOut.match(1); + CheckOutFound = true; + } + else if(!CheckOutFound && this->RegexParent.find(this->Line)) + { + this->BZR->URL = this->RegexParent.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +{ +public: + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): + BZR(bzr), Rev(rev) + { + this->SetLog(&bzr->Log, prefix); + this->RegexRevno.compile("^([0-9]+)$"); + } +private: + cmCTestBZR* BZR; + std::string& Rev; + cmsys::RegularExpression RegexRevno; + virtual bool ProcessLine() + { + if(this->RegexRevno.find(this->Line)) + { + this->Rev = this->RegexRevno.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::LoadInfo() +{ + // Run "bzr info" to get the repository info from the work tree. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_info[] = {bzr, "info", 0}; + InfoParser iout(this, "info-out> "); + OutputLogger ierr(this->Log, "info-err> "); + this->RunChild(bzr_info, &iout, &ierr); + + // Run "bzr revno" to get the repository revision number from the work tree. + const char* bzr_revno[] = {bzr, "revno", 0}; + std::string rev; + RevnoParser rout(this, "revno-out> ", rev); + OutputLogger rerr(this->Log, "revno-err> "); + this->RunChild(bzr_revno, &rout, &rerr); + + return rev; +} + +void cmCTestBZR::NoteOldRevision() +{ + this->OldRevision = this->LoadInfo(); + this->Log << "Revision before update: " << this->OldRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::NoteNewRevision() +{ + this->NewRevision = this->LoadInfo(); + this->Log << "Revision after update: " << this->NewRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + this->Log << "URL = " << this->URL << "\n"; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), EmailRegex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } +private: + cmCTestBZR* BZR; + + typedef cmCTestBZR::Revision Revision; + Revision Rev; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + this->Rev = Revision(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + this->BZR->DoRevision(this->Rev); + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexUpdate; + + virtual bool ProcessChunk(const char* first, int length) + { + bool last_is_new_line = (*first == '\r' || *first == '\n'); + + const char* const last = first + length; + for(const char* c = first; c != last; ++c) + { + if(*c == '\r' || *c == '\n') + { + if(!last_is_new_line) + { + // Log this line. + if(this->Log && this->Prefix) + { + *this->Log << this->Prefix << this->Line << "\n"; + } + + // Hand this line to the subclass implementation. + if(!this->ProcessLine()) + { + this->Line = ""; + return false; + } + + this->Line = ""; + last_is_new_line = true; + } + } + else + { + // Append this character to the line under construction. + this->Line.append(1, *c); + last_is_new_line = false; + } + } + return true; + } + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)[0], + this->RegexUpdate.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + const std::string dir = cmSystemTools::GetFilenamePath(path); + const std::string name = cmSystemTools::GetFilenameName(path); + + if ( c0=='C' ) + { + this->BZR->Dirs[dir][name].Status = PathConflicting; + return; + } + + if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) + { + this->BZR->Dirs[dir][name].Status = PathUpdated; + return; + } + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::GetNightlyOrLatestRevno() +{ + // Parse "bzr log -r curr_rev.. --xml bzr_url" to get a list of all revision numbers + std::string revs = this->OldRevision + ".."; + + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log4revno-out> "); + OutputLogger err(this->Log, "log4revno-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + + // In case no revision occured we keep the old revision + if(this->Revisions.empty()) return this->OldRevision; + + // Get time for nightly build (even though we don't always use it) + // This is in UTC time + struct tm* t = this->CTest->GetNightlyTime( + this->CTest->GetCTestConfiguration("NightlyStartTime"), + this->CTest->GetTomorrowTag()); + + if (t->tm_isdst != 0) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Daylight saving time is used in the result of GetNightlyTime which is supposed to use UTC time. Results might not be quite accurate." + << std::endl); + } + + // To be consistent with UTC time we should not use mktime on UTC-defined + // tm structs. It is OK to do so here since we only need to do some + // ordering comparisons on timestamps. + // A better solution would be to use timegm instead of mktime but + // timegm is not a widely supported function + const time_t nightly_timestamp = mktime(t); + + const bool is_nightly = this->CTest->GetTestModel() == cmCTest::NIGHTLY; + + // If we don't find anything, by default the new revision is the old one + std::string new_revision = this->OldRevision; + + for(std::list<Revision>::const_iterator ri = this->Revisions.begin(); + ri != this->Revisions.end(); ++ri) + { + if (!is_nightly) + { + if(atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + { + new_revision = ri->Rev; + } + } + else + { + // Parse the time of this revision + // curl_getdate does not parse "Tue 2009-04-14 15:36:32 +0200" + // strptime is not available on msvc and it is not clear to me how it handles timezones + // This means we need a workaround + const std::string & date = ri->Date; + struct tm timeinfo; + + cmsys::RegularExpression regex_date( + ".* ([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) ([-+])([0-9][0-9])([0-9][0-9])"); + if (!regex_date.find(date)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to parse bzr commit time: " << date.c_str() + << std::endl); + continue; + } + + // Construct a UTC tm + timeinfo.tm_year = atoi(regex_date.match(1).c_str()) - 1900; + timeinfo.tm_mon = atoi(regex_date.match(2).c_str()) - 1; + timeinfo.tm_mday = atoi(regex_date.match(3).c_str()); + timeinfo.tm_hour = atoi(regex_date.match(4).c_str()); + timeinfo.tm_min = atoi(regex_date.match(5).c_str()); + timeinfo.tm_sec = atoi(regex_date.match(6).c_str()); + + timeinfo.tm_isdst = 0; + // timeinfo.tm_wday; // Should not be used + // timeinfo.tm_yday; // Should not be used + + time_t offset = atoi(regex_date.match(8).c_str())*3600 + + atoi(regex_date.match(9).c_str())*60; + if (regex_date.match(7)=="-") offset = - offset; + + // See previous comment. A nicer solution would be to use timegm + // instead of mktime but this should not impac the results. + const time_t commit_timestamp = mktime(&timeinfo)-offset; + + // Keep it if it is the highest revision number prior to the nightly time + if((atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + && (commit_timestamp <= nightly_timestamp)) + { + new_revision = ri->Rev; + } + } + } + + return new_revision; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + + // Get the revision number used to update the repository + std::string new_revision = this->GetNightlyOrLatestRevno(); + + std::vector<char const*> bzr_update; + bzr_update.push_back(this->CommandLineTool.c_str()); + bzr_update.push_back("pull"); + bzr_update.push_back("-r"); + bzr_update.push_back(new_revision.c_str()); + bzr_update.push_back(this->URL.c_str()); + + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + bzr_update.push_back(ai->c_str()); + } + bzr_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + // For some reason bzr uses cerr to display the update status + return this->RunUpdateCommand(&bzr_update[0], &err, &out); + return false; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::SingleFileLogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + SingleFileLogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), RevPtr(0), EmailRegex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~SingleFileLogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } + + typedef cmCTestBZR::Revision Revision; + Revision * GetListedRev(){return this->RevPtr;} +private: + cmCTestBZR* BZR; + + Revision * RevPtr; + Revision Rev; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + RevPtr = 0; + this->Rev = Revision(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + std::list<Revision>::iterator + it = std::find(this->BZR->Revisions.begin(), this->BZR->Revisions.end(), this->Rev); + if(it!=this->BZR->Revisions.end()) this->RevPtr = &(*it); + else this->RevPtr = 0; + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::DoRevision(Revision const& revision) +{ + // Indicate we found a revision. + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + + // Ignore changes in the old revision. + if(revision.Rev == this->OldRevision) + { + this->PriorRev = revision; + return; + } + + // Store the revision. + this->Revisions.push_back(revision); + + // Report this revision. + Revision const& rev = this->Revisions.back(); + this->Log << "Found revision " << rev.Rev << "\n" + << " author = " << rev.Author << "\n" + << " date = " << rev.Date << "\n"; + + + // Update information about revisions of the changed files. + for(std::map<cmStdString, Directory>::iterator + di = this->Dirs.begin(); di != this->Dirs.end(); ++di) + { + const std::string & dir = di->first; + for(std::map<cmStdString, File>::iterator + fi = (di->second).begin(); fi != (di->second).end(); ++fi) + { + const std::string & name = fi->first; + const std::string path = dir + (dir.empty()?"":"/") + name; + + // Run "bzr log" to get the revision of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revision.Rev.c_str(), "--xml", path.c_str(), 0}; + SingleFileLogParser out(this, "sf-log-out> "); + OutputLogger err(this->Log, "sf-log-err> "); + this->RunChild(bzr_log, &out, &err); + + Revision * rev = out.GetListedRev(); + + if( rev ) + { + File& file = fi->second; + + if (!file.Rev) + { + file.PriorRev = &this->PriorRev; + file.Rev = rev; + } + else if ( atoi(rev->Rev.c_str()) > atoi(file.Rev->Rev.c_str()) ) + { + file.PriorRev = file.Rev; + file.Rev = rev; + } + } + } + } +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + this->Revisions.clear(); + std::string revs; + if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) + { + // DoRevision takes care of discarding the information about OldRevision + revs = this->OldRevision + ".." + this->NewRevision; + } + else + { + return; + } + + // Run "bzr log" to get all global revisions of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)[0], + this->RegexStatus.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + if ( c0=='C' ) + { + this->DoPath(PathConflicting, path); + return; + } + + if ( c0 == '+' || c0 == 'R' || c0 == 'P' + || c1=='M' || c1=='K' || c1=='N' || c1=='D' + || c2 =='*' ) + { + this->DoPath(PathModified, path); + return; + } + } + + void DoPath(PathStatus status, std::string const& path) + { + std::string dir = cmSystemTools::GetFilenamePath(path); + std::string name = cmSystemTools::GetFilenameName(path); + File& file = this->BZR->Dirs[dir][name]; + file.Status = status; + // For local modifications the current rev is unknown and the + // prior rev is the latest from bzr. + if(!file.Rev && !file.PriorRev) + { + file.PriorRev = &this->BZR->PriorRev; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadModifications() +{ + // Run "bzr status" which reports local modifications. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_status[] = {bzr, "status", "-SV", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(bzr_status, &out, &err); +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::WriteXMLDirectory(std::ostream& xml, + std::string const& path, + Directory const& dir) +{ + const char* slash = path.empty()? "":"/"; + xml << "\t<Directory>\n" + << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\n"; + for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) + { + std::string full = path + slash + fi->first; + this->WriteXMLEntry(xml, path, fi->first, full, fi->second); + } + xml << "\t</Directory>\n"; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::WriteXMLUpdates(std::ostream& xml) +{ + this->LoadRevisions(); + this->LoadModifications(); + + for(std::map<cmStdString, Directory>::const_iterator + di = this->Dirs.begin(); di != this->Dirs.end(); ++di) + { + this->WriteXMLDirectory(xml, di->first, di->second); + } + + return true; +} Index: Source/CTest/cmCTestBZR.h =================================================================== RCS file: Source/CTest/cmCTestBZR.h diff -N Source/CTest/cmCTestBZR.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.h 22 Apr 2009 15:39:39 -0000 @@ -0,0 +1,66 @@ +#ifndef cmCTestBZR_h +#define cmCTestBZR_h + +#include "cmCTestVC.h" + +/** \class cmCTestBZR + * \brief Interaction with bzr command-line tool + * + */ +class cmCTestBZR: public cmCTestVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestBZR(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestBZR(); + +private: + // Implement cmCTestVC internal API. + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual std::string GetNightlyOrLatestRevno(); + virtual bool UpdateImpl(); + virtual bool WriteXMLUpdates(std::ostream& xml); + + // Update status for files in each directory. + class Directory: public std::map<cmStdString, File> {}; + std::map<cmStdString, Directory> Dirs; + + // Old and new repository revisions. + std::string OldRevision; + std::string NewRevision; + + // URL of repository directory checked out in the working tree. + std::string URL; + + // Information known about old revision. + Revision PriorRev; + + // Information about revisions from a svn log. + std::list<Revision> Revisions; + + std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + void DoRevision(Revision const& revision); + void WriteXMLDirectory(std::ostream& xml, std::string const& path, + Directory const& dir); + + // Parsing helper classes. + class InfoParser; + class RevnoParser; + class LogParser; + class UpdateParser; + class StatusParser; + class SingleFileLogParser; + friend class InfoParser; + friend class RevnoParser; + friend class LogParser; + friend class UpdateParser; + friend class StatusParser; + friend class SingleFileLogParser; +}; + +#endif Index: Source/CTest/cmCTestUpdateCommand.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v retrieving revision 1.16 diff -u -r1.16 cmCTestUpdateCommand.cxx --- Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 13:19:03 -0000 1.16 +++ Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 15:39:39 -0000 @@ -49,6 +49,10 @@ this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITCommand", "CTEST_GIT_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); Index: Source/CTest/cmCTestUpdateHandler.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v retrieving revision 1.65 diff -u -r1.65 cmCTestUpdateHandler.cxx --- Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 13:19:03 -0000 1.65 +++ Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 15:39:39 -0000 @@ -30,6 +30,7 @@ #include "cmCTestVC.h" #include "cmCTestCVS.h" #include "cmCTestSVN.h" +#include "cmCTestBZR.h" #include "cmCTestGIT.h" #include <cmsys/auto_ptr.hxx> @@ -52,6 +53,7 @@ "Unknown", "CVS", "SVN", + "BZR", "GIT" }; @@ -135,6 +137,10 @@ { 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; @@ -153,6 +159,10 @@ { 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; @@ -214,6 +224,7 @@ { case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; + case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } @@ -349,6 +360,12 @@ return cmCTestUpdateHandler::e_CVS; } sourceDirectory = dir; + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } + sourceDirectory = dir; sourceDirectory += "/.git"; if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) { @@ -381,6 +398,7 @@ { case e_CVS: key = "CVSCommand"; break; case e_SVN: key = "SVNCommand"; break; + case e_BZR: key = "BZRCommand"; break; case e_GIT: key = "GITCommand"; break; default: break; } Index: Source/CTest/cmCTestUpdateHandler.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v retrieving revision 1.12 diff -u -r1.12 cmCTestUpdateHandler.h --- Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 13:19:04 -0000 1.12 +++ Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 15:39:39 -0000 @@ -46,6 +46,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_GIT, e_LAST }; Index: Source/CTest/cmCTestVC.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestVC.h,v retrieving revision 1.8 diff -u -r1.8 cmCTestVC.h --- Source/CTest/cmCTestVC.h 26 Feb 2009 14:22:32 -0000 1.8 +++ Source/CTest/cmCTestVC.h 22 Apr 2009 15:39:39 -0000 @@ -79,6 +79,15 @@ std::string Date; std::string Author; std::string Log; + + bool operator==(const Revision& other) + { + if (this->Rev!=other.Rev) return false; + if (this->Date!=other.Date) return false; + if (this->Author!=other.Author) return false; + if (this->Log!=other.Log) return false; + return true; + } }; struct File; Index: Modules/CTest.cmake =================================================================== RCS file: /cvsroot/CMake/CMake/Modules/CTest.cmake,v retrieving revision 1.14 diff -u -r1.14 CTest.cmake --- Modules/CTest.cmake 11 Feb 2009 20:18:13 -0000 1.14 +++ Modules/CTest.cmake 22 Apr 2009 15:39:39 -0000 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -129,6 +134,11 @@ IF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") + IF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") ENDIF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") ENDIF(UPDATE_TYPE MATCHES "[Cc][Vv][Ss]") @@ -206,8 +216,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE ![]() Index: Source/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CMakeLists.txt,v retrieving revision 1.415 diff -u -r1.415 CMakeLists.txt --- Source/CMakeLists.txt 22 Apr 2009 13:18:57 -0000 1.415 +++ Source/CMakeLists.txt 22 Apr 2009 16:28:41 -0000 @@ -361,6 +361,8 @@ CTest/cmCTestCVS.h CTest/cmCTestSVN.cxx CTest/cmCTestSVN.h + CTest/cmCTestBZR.cxx + CTest/cmCTestBZR.h CTest/cmCTestGIT.cxx CTest/cmCTestGIT.h ) Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: Source/CTest/cmCTestBZR.cxx diff -N Source/CTest/cmCTestBZR.cxx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.cxx 22 Apr 2009 16:28:42 -0000 @@ -0,0 +1,959 @@ +#include "cmCTestBZR.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + +#include <cm_expat.h> + +//---------------------------------------------------------------------------- +extern "C" +{ +int cmBZRXMLParserUnknownEncodingHandler(void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info) +{ + static const int latin1[]= + { + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0005, + 0x0006, + 0x0007, + 0x0008, + 0x0009, + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x000E, + 0x000F, + 0x0010, + 0x0011, + 0x0012, + 0x0013, + 0x0014, + 0x0015, + 0x0016, + 0x0017, + 0x0018, + 0x0019, + 0x001A, + 0x001B, + 0x001C, + 0x001D, + 0x001E, + 0x001F, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x20AC, + 0x0081, + 0x201A, + 0x0192, + 0x201E, + 0x2026, + 0x2020, + 0x2021, + 0x02C6, + 0x2030, + 0x0160, + 0x2039, + 0x0152, + 0x008D, + 0x017D, + 0x008F, + 0x0090, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, + 0x2013, + 0x2014, + 0x02DC, + 0x2122, + 0x0161, + 0x203A, + 0x0153, + 0x009D, + 0x017E, + 0x0178, + 0x00A0, + 0x00A1, + 0x00A2, + 0x00A3, + 0x00A4, + 0x00A5, + 0x00A6, + 0x00A7, + 0x00A8, + 0x00A9, + 0x00AA, + 0x00AB, + 0x00AC, + 0x00AD, + 0x00AE, + 0x00AF, + 0x00B0, + 0x00B1, + 0x00B2, + 0x00B3, + 0x00B4, + 0x00B5, + 0x00B6, + 0x00B7, + 0x00B8, + 0x00B9, + 0x00BA, + 0x00BB, + 0x00BC, + 0x00BD, + 0x00BE, + 0x00BF, + 0x00C0, + 0x00C1, + 0x00C2, + 0x00C3, + 0x00C4, + 0x00C5, + 0x00C6, + 0x00C7, + 0x00C8, + 0x00C9, + 0x00CA, + 0x00CB, + 0x00CC, + 0x00CD, + 0x00CE, + 0x00CF, + 0x00D0, + 0x00D1, + 0x00D2, + 0x00D3, + 0x00D4, + 0x00D5, + 0x00D6, + 0x00D7, + 0x00D8, + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x00DD, + 0x00DE, + 0x00DF, + 0x00E0, + 0x00E1, + 0x00E2, + 0x00E3, + 0x00E4, + 0x00E5, + 0x00E6, + 0x00E7, + 0x00E8, + 0x00E9, + 0x00EA, + 0x00EB, + 0x00EC, + 0x00ED, + 0x00EE, + 0x00EF, + 0x00F0, + 0x00F1, + 0x00F2, + 0x00F3, + 0x00F4, + 0x00F5, + 0x00F6, + 0x00F7, + 0x00F8, + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x00FD, + 0x00FE, + 0x00FF + }; + + if ( name==std::string("ascii") || name==std::string("cp1252") ) + { + for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; + return 1; + } + + return 0; +} +} + +//---------------------------------------------------------------------------- +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): + cmCTestGlobalVC(ct, log) +{ + this->PriorRev = this->Unknown; + // Even though it is specified in the documention, with bzr 1.13 + // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. + // Since it doesn't hurt, we specify this environment variable. + cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); +} + +//---------------------------------------------------------------------------- +cmCTestBZR::~cmCTestBZR() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +{ +public: + InfoParser(cmCTestBZR* bzr, const char* prefix): + BZR(bzr), CheckOutFound(false) + { + this->SetLog(&bzr->Log, prefix); + this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); + this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); + } +private: + cmCTestBZR* BZR; + bool CheckOutFound; + cmsys::RegularExpression RegexCheckOut; + cmsys::RegularExpression RegexParent; + virtual bool ProcessLine() + { + if(this->RegexCheckOut.find(this->Line)) + { + this->BZR->URL = this->RegexCheckOut.match(1); + CheckOutFound = true; + } + else if(!CheckOutFound && this->RegexParent.find(this->Line)) + { + this->BZR->URL = this->RegexParent.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +{ +public: + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): + BZR(bzr), Rev(rev) + { + this->SetLog(&bzr->Log, prefix); + this->RegexRevno.compile("^([0-9]+)$"); + } +private: + cmCTestBZR* BZR; + std::string& Rev; + cmsys::RegularExpression RegexRevno; + virtual bool ProcessLine() + { + if(this->RegexRevno.find(this->Line)) + { + this->Rev = this->RegexRevno.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::LoadInfo() +{ + // Run "bzr info" to get the repository info from the work tree. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_info[] = {bzr, "info", 0}; + InfoParser iout(this, "info-out> "); + OutputLogger ierr(this->Log, "info-err> "); + this->RunChild(bzr_info, &iout, &ierr); + + // Run "bzr revno" to get the repository revision number from the work tree. + const char* bzr_revno[] = {bzr, "revno", 0}; + std::string rev; + RevnoParser rout(this, "revno-out> ", rev); + OutputLogger rerr(this->Log, "revno-err> "); + this->RunChild(bzr_revno, &rout, &rerr); + + return rev; +} + +void cmCTestBZR::NoteOldRevision() +{ + this->OldRevision = this->LoadInfo(); + this->Log << "Revision before update: " << this->OldRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::NoteNewRevision() +{ + this->NewRevision = this->LoadInfo(); + this->Log << "Revision after update: " << this->NewRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + this->Log << "URL = " << this->URL << "\n"; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), EmailRegex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } +private: + cmCTestBZR* BZR; + + typedef cmCTestBZR::Revision Revision; + Revision Rev; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + this->Rev = Revision(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + this->BZR->DoRevision(this->Rev); + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexUpdate; + + virtual bool ProcessChunk(const char* first, int length) + { + bool last_is_new_line = (*first == '\r' || *first == '\n'); + + const char* const last = first + length; + for(const char* c = first; c != last; ++c) + { + if(*c == '\r' || *c == '\n') + { + if(!last_is_new_line) + { + // Log this line. + if(this->Log && this->Prefix) + { + *this->Log << this->Prefix << this->Line << "\n"; + } + + // Hand this line to the subclass implementation. + if(!this->ProcessLine()) + { + this->Line = ""; + return false; + } + + this->Line = ""; + last_is_new_line = true; + } + } + else + { + // Append this character to the line under construction. + this->Line.append(1, *c); + last_is_new_line = false; + } + } + return true; + } + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)[0], + this->RegexUpdate.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + const std::string dir = cmSystemTools::GetFilenamePath(path); + const std::string name = cmSystemTools::GetFilenameName(path); + + if ( c0=='C' ) + { + this->BZR->Dirs[dir][name].Status = PathConflicting; + return; + } + + if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) + { + this->BZR->Dirs[dir][name].Status = PathUpdated; + return; + } + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::GetNightlyOrLatestRevno() +{ + // Parse "bzr log -r curr_rev.. --xml bzr_url" to get a list of all revision numbers + std::string revs = this->OldRevision + ".."; + + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log4revno-out> "); + OutputLogger err(this->Log, "log4revno-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + + // In case no revision occured we keep the old revision + if(this->Revisions.empty()) return this->OldRevision; + + // Get time for nightly build (even though we don't always use it) + // This is in UTC time + struct tm* t = this->CTest->GetNightlyTime( + this->CTest->GetCTestConfiguration("NightlyStartTime"), + this->CTest->GetTomorrowTag()); + + if (t->tm_isdst != 0) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Daylight saving time is used in the result of GetNightlyTime which is supposed to use UTC time. Results might not be quite accurate." + << std::endl); + } + + // To be consistent with UTC time we should not use mktime on UTC-defined + // tm structs. It is OK to do so here since we only need to do some + // ordering comparisons on timestamps. + // A better solution would be to use timegm instead of mktime but + // timegm is not a widely supported function + const time_t nightly_timestamp = mktime(t); + + const bool is_nightly = this->CTest->GetTestModel() == cmCTest::NIGHTLY; + + // If we don't find anything, by default the new revision is the old one + std::string new_revision = this->OldRevision; + + for(std::list<Revision>::const_iterator ri = this->Revisions.begin(); + ri != this->Revisions.end(); ++ri) + { + if (!is_nightly) + { + if(atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + { + new_revision = ri->Rev; + } + } + else + { + // Parse the time of this revision + // curl_getdate does not parse "Tue 2009-04-14 15:36:32 +0200" + // strptime is not available on msvc and it is not clear to me how it handles timezones + // This means we need a workaround + const std::string & date = ri->Date; + struct tm timeinfo; + + cmsys::RegularExpression regex_date( + ".* ([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) ([-+])([0-9][0-9])([0-9][0-9])"); + if (!regex_date.find(date)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to parse bzr commit time: " << date.c_str() + << std::endl); + continue; + } + + // Construct a UTC tm + timeinfo.tm_year = atoi(regex_date.match(1).c_str()) - 1900; + timeinfo.tm_mon = atoi(regex_date.match(2).c_str()) - 1; + timeinfo.tm_mday = atoi(regex_date.match(3).c_str()); + timeinfo.tm_hour = atoi(regex_date.match(4).c_str()); + timeinfo.tm_min = atoi(regex_date.match(5).c_str()); + timeinfo.tm_sec = atoi(regex_date.match(6).c_str()); + + timeinfo.tm_isdst = 0; + // timeinfo.tm_wday; // Should not be used + // timeinfo.tm_yday; // Should not be used + + time_t offset = atoi(regex_date.match(8).c_str())*3600 + + atoi(regex_date.match(9).c_str())*60; + if (regex_date.match(7)=="-") offset = - offset; + + // See previous comment. A nicer solution would be to use timegm + // instead of mktime but this should not impac the results. + const time_t commit_timestamp = mktime(&timeinfo)-offset; + + // Keep it if it is the highest revision number prior to the nightly time + if((atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + && (commit_timestamp <= nightly_timestamp)) + { + new_revision = ri->Rev; + } + } + } + + return new_revision; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + + // Get the revision number used to update the repository + std::string new_revision = this->GetNightlyOrLatestRevno(); + + std::vector<char const*> bzr_update; + bzr_update.push_back(this->CommandLineTool.c_str()); + bzr_update.push_back("pull"); + bzr_update.push_back("-r"); + bzr_update.push_back(new_revision.c_str()); + bzr_update.push_back(this->URL.c_str()); + + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + bzr_update.push_back(ai->c_str()); + } + bzr_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + // For some reason bzr uses cerr to display the update status + return this->RunUpdateCommand(&bzr_update[0], &err, &out); + return false; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::SingleFileLogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + SingleFileLogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), RevPtr(0), EmailRegex("(.*) *<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~SingleFileLogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } + + typedef cmCTestBZR::Revision Revision; + Revision * GetListedRev(){return this->RevPtr;} +private: + cmCTestBZR* BZR; + + Revision * RevPtr; + Revision Rev; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + RevPtr = 0; + this->Rev = Revision(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + std::list<Revision>::iterator + it = std::find(this->BZR->Revisions.begin(), this->BZR->Revisions.end(), this->Rev); + if(it!=this->BZR->Revisions.end()) this->RevPtr = &(*it); + else this->RevPtr = 0; + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::DoRevision(Revision const& revision) +{ + // Indicate we found a revision. + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + + // Ignore changes in the old revision. + if(revision.Rev == this->OldRevision) + { + this->PriorRev = revision; + return; + } + + // Store the revision. + this->Revisions.push_back(revision); + + // Report this revision. + Revision const& rev = this->Revisions.back(); + this->Log << "Found revision " << rev.Rev << "\n" + << " author = " << rev.Author << "\n" + << " date = " << rev.Date << "\n"; + + + // Update information about revisions of the changed files. + for(std::map<cmStdString, Directory>::iterator + di = this->Dirs.begin(); di != this->Dirs.end(); ++di) + { + const std::string & dir = di->first; + for(std::map<cmStdString, File>::iterator + fi = (di->second).begin(); fi != (di->second).end(); ++fi) + { + const std::string & name = fi->first; + const std::string path = dir + (dir.empty()?"":"/") + name; + + // Run "bzr log" to get the revision of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revision.Rev.c_str(), "--xml", path.c_str(), 0}; + SingleFileLogParser out(this, "sf-log-out> "); + OutputLogger err(this->Log, "sf-log-err> "); + this->RunChild(bzr_log, &out, &err); + + Revision * rev = out.GetListedRev(); + + if( rev ) + { + File& file = fi->second; + + if (!file.Rev) + { + file.PriorRev = &this->PriorRev; + file.Rev = rev; + } + else if ( atoi(rev->Rev.c_str()) > atoi(file.Rev->Rev.c_str()) ) + { + file.PriorRev = file.Rev; + file.Rev = rev; + } + } + } + } +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + this->Revisions.clear(); + std::string revs; + if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) + { + // DoRevision takes care of discarding the information about OldRevision + revs = this->OldRevision + ".." + this->NewRevision; + } + else + { + return; + } + + // Run "bzr log" to get all global revisions of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)[0], + this->RegexStatus.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + if ( c0=='C' ) + { + this->DoPath(PathConflicting, path); + return; + } + + if ( c0 == '+' || c0 == 'R' || c0 == 'P' + || c1=='M' || c1=='K' || c1=='N' || c1=='D' + || c2 =='*' ) + { + this->DoPath(PathModified, path); + return; + } + } + + void DoPath(PathStatus status, std::string const& path) + { + std::string dir = cmSystemTools::GetFilenamePath(path); + std::string name = cmSystemTools::GetFilenameName(path); + File& file = this->BZR->Dirs[dir][name]; + file.Status = status; + // For local modifications the current rev is unknown and the + // prior rev is the latest from bzr. + if(!file.Rev && !file.PriorRev) + { + file.PriorRev = &this->BZR->PriorRev; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadModifications() +{ + // Run "bzr status" which reports local modifications. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_status[] = {bzr, "status", "-SV", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(bzr_status, &out, &err); +} Index: Source/CTest/cmCTestBZR.h =================================================================== RCS file: Source/CTest/cmCTestBZR.h diff -N Source/CTest/cmCTestBZR.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.h 22 Apr 2009 16:28:42 -0000 @@ -0,0 +1,49 @@ +#ifndef cmCTestBZR_h +#define cmCTestBZR_h + +#include "cmCTestGlobalVC.h" + +/** \class cmCTestBZR + * \brief Interaction with bzr command-line tool + * + */ +class cmCTestBZR: public cmCTestGlobalVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestBZR(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestBZR(); + +private: + // Implement cmCTestVC internal API. + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual std::string GetNightlyOrLatestRevno(); + virtual bool UpdateImpl(); + + // URL of repository directory checked out in the working tree. + std::string URL; + + virtual std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + virtual void DoRevision(Revision const& revision); + + // Parsing helper classes. + class InfoParser; + class RevnoParser; + class LogParser; + class UpdateParser; + class StatusParser; + class SingleFileLogParser; + friend class InfoParser; + friend class RevnoParser; + friend class LogParser; + friend class UpdateParser; + friend class StatusParser; + friend class SingleFileLogParser; +}; + +#endif Index: Source/CTest/cmCTestUpdateCommand.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v retrieving revision 1.16 diff -u -r1.16 cmCTestUpdateCommand.cxx --- Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 13:19:03 -0000 1.16 +++ Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 16:28:42 -0000 @@ -49,6 +49,10 @@ this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITCommand", "CTEST_GIT_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); Index: Source/CTest/cmCTestUpdateHandler.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v retrieving revision 1.65 diff -u -r1.65 cmCTestUpdateHandler.cxx --- Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 13:19:03 -0000 1.65 +++ Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 16:28:42 -0000 @@ -30,6 +30,7 @@ #include "cmCTestVC.h" #include "cmCTestCVS.h" #include "cmCTestSVN.h" +#include "cmCTestBZR.h" #include "cmCTestGIT.h" #include <cmsys/auto_ptr.hxx> @@ -52,6 +53,7 @@ "Unknown", "CVS", "SVN", + "BZR", "GIT" }; @@ -135,6 +137,10 @@ { 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; @@ -153,6 +159,10 @@ { 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; @@ -214,6 +224,7 @@ { case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; + case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } @@ -349,6 +360,12 @@ return cmCTestUpdateHandler::e_CVS; } sourceDirectory = dir; + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } + sourceDirectory = dir; sourceDirectory += "/.git"; if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) { @@ -381,6 +398,7 @@ { case e_CVS: key = "CVSCommand"; break; case e_SVN: key = "SVNCommand"; break; + case e_BZR: key = "BZRCommand"; break; case e_GIT: key = "GITCommand"; break; default: break; } Index: Source/CTest/cmCTestUpdateHandler.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v retrieving revision 1.12 diff -u -r1.12 cmCTestUpdateHandler.h --- Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 13:19:04 -0000 1.12 +++ Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 16:28:42 -0000 @@ -46,6 +46,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_GIT, e_LAST }; Index: Source/CTest/cmCTestVC.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestVC.h,v retrieving revision 1.8 diff -u -r1.8 cmCTestVC.h --- Source/CTest/cmCTestVC.h 26 Feb 2009 14:22:32 -0000 1.8 +++ Source/CTest/cmCTestVC.h 22 Apr 2009 16:28:42 -0000 @@ -79,6 +79,15 @@ std::string Date; std::string Author; std::string Log; + + bool operator==(const Revision& other) + { + if (this->Rev!=other.Rev) return false; + if (this->Date!=other.Date) return false; + if (this->Author!=other.Author) return false; + if (this->Log!=other.Log) return false; + return true; + } }; struct File; Index: Modules/CTest.cmake =================================================================== RCS file: /cvsroot/CMake/CMake/Modules/CTest.cmake,v retrieving revision 1.14 diff -u -r1.14 CTest.cmake --- Modules/CTest.cmake 11 Feb 2009 20:18:13 -0000 1.14 +++ Modules/CTest.cmake 22 Apr 2009 16:28:42 -0000 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -129,6 +134,11 @@ IF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") + IF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") ENDIF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") ENDIF(UPDATE_TYPE MATCHES "[Cc][Vv][Ss]") @@ -206,8 +216,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE ![]() Index: Modules/CTest.cmake =================================================================== RCS file: /cvsroot/CMake/CMake/Modules/CTest.cmake,v retrieving revision 1.14 diff -u -r1.14 CTest.cmake --- Modules/CTest.cmake 11 Feb 2009 20:18:13 -0000 1.14 +++ Modules/CTest.cmake 23 Apr 2009 10:21:27 -0000 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -129,6 +134,11 @@ IF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") + IF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF(UPDATE_TYPE MATCHES "[Bb][Zz][Rr]") ENDIF(UPDATE_TYPE MATCHES "[Ss][Vv][Nn]") ENDIF(UPDATE_TYPE MATCHES "[Cc][Vv][Ss]") @@ -206,8 +216,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE Index: Source/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CMakeLists.txt,v retrieving revision 1.415 diff -u -r1.415 CMakeLists.txt --- Source/CMakeLists.txt 22 Apr 2009 13:18:57 -0000 1.415 +++ Source/CMakeLists.txt 23 Apr 2009 10:21:28 -0000 @@ -361,6 +361,8 @@ CTest/cmCTestCVS.h CTest/cmCTestSVN.cxx CTest/cmCTestSVN.h + CTest/cmCTestBZR.cxx + CTest/cmCTestBZR.h CTest/cmCTestGIT.cxx CTest/cmCTestGIT.h ) Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: Source/CTest/cmCTestBZR.cxx diff -N Source/CTest/cmCTestBZR.cxx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.cxx 23 Apr 2009 10:21:28 -0000 @@ -0,0 +1,833 @@ +#include "cmCTestBZR.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + +#include <cm_expat.h> + +//---------------------------------------------------------------------------- +extern "C" +{ +int cmBZRXMLParserUnknownEncodingHandler(void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info) +{ + static const int latin1[]= + { + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0005, + 0x0006, + 0x0007, + 0x0008, + 0x0009, + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x000E, + 0x000F, + 0x0010, + 0x0011, + 0x0012, + 0x0013, + 0x0014, + 0x0015, + 0x0016, + 0x0017, + 0x0018, + 0x0019, + 0x001A, + 0x001B, + 0x001C, + 0x001D, + 0x001E, + 0x001F, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x20AC, + 0x0081, + 0x201A, + 0x0192, + 0x201E, + 0x2026, + 0x2020, + 0x2021, + 0x02C6, + 0x2030, + 0x0160, + 0x2039, + 0x0152, + 0x008D, + 0x017D, + 0x008F, + 0x0090, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, + 0x2013, + 0x2014, + 0x02DC, + 0x2122, + 0x0161, + 0x203A, + 0x0153, + 0x009D, + 0x017E, + 0x0178, + 0x00A0, + 0x00A1, + 0x00A2, + 0x00A3, + 0x00A4, + 0x00A5, + 0x00A6, + 0x00A7, + 0x00A8, + 0x00A9, + 0x00AA, + 0x00AB, + 0x00AC, + 0x00AD, + 0x00AE, + 0x00AF, + 0x00B0, + 0x00B1, + 0x00B2, + 0x00B3, + 0x00B4, + 0x00B5, + 0x00B6, + 0x00B7, + 0x00B8, + 0x00B9, + 0x00BA, + 0x00BB, + 0x00BC, + 0x00BD, + 0x00BE, + 0x00BF, + 0x00C0, + 0x00C1, + 0x00C2, + 0x00C3, + 0x00C4, + 0x00C5, + 0x00C6, + 0x00C7, + 0x00C8, + 0x00C9, + 0x00CA, + 0x00CB, + 0x00CC, + 0x00CD, + 0x00CE, + 0x00CF, + 0x00D0, + 0x00D1, + 0x00D2, + 0x00D3, + 0x00D4, + 0x00D5, + 0x00D6, + 0x00D7, + 0x00D8, + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x00DD, + 0x00DE, + 0x00DF, + 0x00E0, + 0x00E1, + 0x00E2, + 0x00E3, + 0x00E4, + 0x00E5, + 0x00E6, + 0x00E7, + 0x00E8, + 0x00E9, + 0x00EA, + 0x00EB, + 0x00EC, + 0x00ED, + 0x00EE, + 0x00EF, + 0x00F0, + 0x00F1, + 0x00F2, + 0x00F3, + 0x00F4, + 0x00F5, + 0x00F6, + 0x00F7, + 0x00F8, + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x00FD, + 0x00FE, + 0x00FF + }; + + if ( name==std::string("ascii") || name==std::string("cp1252") ) + { + for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; + return 1; + } + + return 0; +} +} + +//---------------------------------------------------------------------------- +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): + cmCTestGlobalVC(ct, log) +{ + this->PriorRev = this->Unknown; + // Even though it is specified in the documention, with bzr 1.13 + // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. + // Since it doesn't hurt, we specify this environment variable. + cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); +} + +//---------------------------------------------------------------------------- +cmCTestBZR::~cmCTestBZR() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +{ +public: + InfoParser(cmCTestBZR* bzr, const char* prefix): + BZR(bzr), CheckOutFound(false) + { + this->SetLog(&bzr->Log, prefix); + this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); + this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); + } +private: + cmCTestBZR* BZR; + bool CheckOutFound; + cmsys::RegularExpression RegexCheckOut; + cmsys::RegularExpression RegexParent; + virtual bool ProcessLine() + { + if(this->RegexCheckOut.find(this->Line)) + { + this->BZR->URL = this->RegexCheckOut.match(1); + CheckOutFound = true; + } + else if(!CheckOutFound && this->RegexParent.find(this->Line)) + { + this->BZR->URL = this->RegexParent.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +{ +public: + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): + BZR(bzr), Rev(rev) + { + this->SetLog(&bzr->Log, prefix); + this->RegexRevno.compile("^([0-9]+)$"); + } +private: + cmCTestBZR* BZR; + std::string& Rev; + cmsys::RegularExpression RegexRevno; + virtual bool ProcessLine() + { + if(this->RegexRevno.find(this->Line)) + { + this->Rev = this->RegexRevno.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::LoadInfo() +{ + // Run "bzr info" to get the repository info from the work tree. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_info[] = {bzr, "info", 0}; + InfoParser iout(this, "info-out> "); + OutputLogger ierr(this->Log, "info-err> "); + this->RunChild(bzr_info, &iout, &ierr); + + // Run "bzr revno" to get the repository revision number from the work tree. + const char* bzr_revno[] = {bzr, "revno", 0}; + std::string rev; + RevnoParser rout(this, "revno-out> ", rev); + OutputLogger rerr(this->Log, "revno-err> "); + this->RunChild(bzr_revno, &rout, &rerr); + + return rev; +} + +void cmCTestBZR::NoteOldRevision() +{ + this->OldRevision = this->LoadInfo(); + this->Log << "Revision before update: " << this->OldRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::NoteNewRevision() +{ + this->NewRevision = this->LoadInfo(); + this->Log << "Revision after update: " << this->NewRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + this->Log << "URL = " << this->URL << "\n"; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), + EmailRegex("(.*) <([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } +private: + cmCTestBZR* BZR; + + typedef cmCTestBZR::Revision Revision; + typedef cmCTestBZR::Change Change; + Revision Rev; + std::vector<Change> Changes; + Change CurChange; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + this->Rev = Revision(); + this->Changes.clear(); + } + // affected-files can contain blocks of + // modified, unknown, renamed, kind-changed, removed, conflicts, added + else if(strcmp(name, "modified") == 0 + || strcmp(name, "renamed") == 0 + || strcmp(name, "kind-changed") == 0) + { + this->CurChange = Change(); + this->CurChange.Action = 'M'; + } + else if(strcmp(name, "added") == 0) + { + this->CurChange = Change(); + this->CurChange = 'A'; + } + else if(strcmp(name, "removed") == 0) + { + this->CurChange = Change(); + this->CurChange = 'D'; + } + else if(strcmp(name, "unknown") == 0 + || strcmp(name, "conflicts") == 0) + { + // Should not happen here + this->CurChange = Change(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + this->BZR->DoRevision(this->Rev, this->Changes); + } + else if((strcmp(name, "file") == 0 || strcmp(name, "directory") == 0) + && !this->CData.empty()) + { + this->CurChange.Path.assign(&this->CData[0], this->CData.size()); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "symlink") == 0 && !this->CData.empty()) + { + // symlinks have an arobase at the end in the log + this->CurChange.Path.assign(&this->CData[0], this->CData.size()-1); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexUpdate; + + virtual bool ProcessChunk(const char* first, int length) + { + bool last_is_new_line = (*first == '\r' || *first == '\n'); + + const char* const last = first + length; + for(const char* c = first; c != last; ++c) + { + if(*c == '\r' || *c == '\n') + { + if(!last_is_new_line) + { + // Log this line. + if(this->Log && this->Prefix) + { + *this->Log << this->Prefix << this->Line << "\n"; + } + + // Hand this line to the subclass implementation. + if(!this->ProcessLine()) + { + this->Line = ""; + return false; + } + + this->Line = ""; + last_is_new_line = true; + } + } + else + { + // Append this character to the line under construction. + this->Line.append(1, *c); + last_is_new_line = false; + } + } + return true; + } + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)[0], + this->RegexUpdate.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + const std::string dir = cmSystemTools::GetFilenamePath(path); + const std::string name = cmSystemTools::GetFilenameName(path); + + if ( c0=='C' ) + { + this->BZR->Dirs[dir][name].Status = PathConflicting; + return; + } + + if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) + { + this->BZR->Dirs[dir][name].Status = PathUpdated; + return; + } + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::GetNightlyOrLatestRevno() +{ + // Parse "bzr log -r curr_rev.. --xml bzr_url" to get a list of all revision numbers + std::string revs = this->OldRevision + ".."; + + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log4revno-out> "); + OutputLogger err(this->Log, "log4revno-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + + // In case no revision occured we keep the old revision + if(this->Revisions.empty()) return this->OldRevision; + + // Get time for nightly build (even though we don't always use it) + // This is in UTC time + struct tm* t = this->CTest->GetNightlyTime( + this->CTest->GetCTestConfiguration("NightlyStartTime"), + this->CTest->GetTomorrowTag()); + + if (t->tm_isdst != 0) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Daylight saving time is used in the result of GetNightlyTime which is supposed to use UTC time. Results might not be quite accurate." + << std::endl); + } + + // To be consistent with UTC time we should not use mktime on UTC-defined + // tm structs. It is OK to do so here since we only need to do some + // ordering comparisons on timestamps. + // A better solution would be to use timegm instead of mktime but + // timegm is not a widely supported function + const time_t nightly_timestamp = mktime(t); + + const bool is_nightly = this->CTest->GetTestModel() == cmCTest::NIGHTLY; + + // If we don't find anything, by default the new revision is the old one + std::string new_revision = this->OldRevision; + + for(std::list<Revision>::const_iterator ri = this->Revisions.begin(); + ri != this->Revisions.end(); ++ri) + { + if (!is_nightly) + { + if(atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + { + new_revision = ri->Rev; + } + } + else + { + // Parse the time of this revision + // curl_getdate does not parse "Tue 2009-04-14 15:36:32 +0200" + // strptime is not available on msvc and it is not clear to me how it handles timezones + // This means we need a workaround + const std::string & date = ri->Date; + struct tm timeinfo; + + cmsys::RegularExpression regex_date( + ".* ([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) ([-+])([0-9][0-9])([0-9][0-9])"); + if (!regex_date.find(date)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to parse bzr commit time: " << date.c_str() + << std::endl); + continue; + } + + // Construct a UTC tm + timeinfo.tm_year = atoi(regex_date.match(1).c_str()) - 1900; + timeinfo.tm_mon = atoi(regex_date.match(2).c_str()) - 1; + timeinfo.tm_mday = atoi(regex_date.match(3).c_str()); + timeinfo.tm_hour = atoi(regex_date.match(4).c_str()); + timeinfo.tm_min = atoi(regex_date.match(5).c_str()); + timeinfo.tm_sec = atoi(regex_date.match(6).c_str()); + + timeinfo.tm_isdst = 0; + // timeinfo.tm_wday; // Should not be used + // timeinfo.tm_yday; // Should not be used + + time_t offset = atoi(regex_date.match(8).c_str())*3600 + + atoi(regex_date.match(9).c_str())*60; + if (regex_date.match(7)=="-") offset = - offset; + + // See previous comment. A nicer solution would be to use timegm + // instead of mktime but this should not impac the results. + const time_t commit_timestamp = mktime(&timeinfo)-offset; + + // Keep it if it is the highest revision number prior to the nightly time + if((atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + && (commit_timestamp <= nightly_timestamp)) + { + new_revision = ri->Rev; + } + } + } + + return new_revision; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + + // Get the revision number used to update the repository + std::string new_revision = this->GetNightlyOrLatestRevno(); + + std::vector<char const*> bzr_update; + bzr_update.push_back(this->CommandLineTool.c_str()); + bzr_update.push_back("pull"); + bzr_update.push_back("-r"); + bzr_update.push_back(new_revision.c_str()); + bzr_update.push_back(this->URL.c_str()); + + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + bzr_update.push_back(ai->c_str()); + } + bzr_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + // For some reason bzr uses cerr to display the update status + return this->RunUpdateCommand(&bzr_update[0], &err, &out); + return false; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + this->Revisions.clear(); + std::string revs; + if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) + { + // DoRevision takes care of discarding the information about OldRevision + revs = this->OldRevision + ".." + this->NewRevision; + } + else + { + return; + } + + // Run "bzr log" to get all global revisions of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-v", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)[0], + this->RegexStatus.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + if ( c0=='C' ) + { + this->BZR->DoModification(PathConflicting, path); + return; + } + + if ( c0 == '+' || c0 == 'R' || c0 == 'P' + || c1=='M' || c1=='K' || c1=='N' || c1=='D' + || c2 =='*' ) + { + this->BZR->DoModification(PathModified, path); + return; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadModifications() +{ + // Run "bzr status" which reports local modifications. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_status[] = {bzr, "status", "-SV", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(bzr_status, &out, &err); +} Index: Source/CTest/cmCTestBZR.h =================================================================== RCS file: Source/CTest/cmCTestBZR.h diff -N Source/CTest/cmCTestBZR.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.h 23 Apr 2009 10:21:28 -0000 @@ -0,0 +1,45 @@ +#ifndef cmCTestBZR_h +#define cmCTestBZR_h + +#include "cmCTestGlobalVC.h" + +/** \class cmCTestBZR + * \brief Interaction with bzr command-line tool + * + */ +class cmCTestBZR: public cmCTestGlobalVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestBZR(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestBZR(); + +private: + // Implement cmCTestVC internal API. + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual std::string GetNightlyOrLatestRevno(); + virtual bool UpdateImpl(); + + // URL of repository directory checked out in the working tree. + std::string URL; + + std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + // Parsing helper classes. + class InfoParser; + class RevnoParser; + class LogParser; + class UpdateParser; + class StatusParser; + friend class InfoParser; + friend class RevnoParser; + friend class LogParser; + friend class UpdateParser; + friend class StatusParser; +}; + +#endif Index: Source/CTest/cmCTestUpdateCommand.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v retrieving revision 1.16 diff -u -r1.16 cmCTestUpdateCommand.cxx --- Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 13:19:03 -0000 1.16 +++ Source/CTest/cmCTestUpdateCommand.cxx 23 Apr 2009 10:21:28 -0000 @@ -49,6 +49,10 @@ this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITCommand", "CTEST_GIT_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); Index: Source/CTest/cmCTestUpdateHandler.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v retrieving revision 1.65 diff -u -r1.65 cmCTestUpdateHandler.cxx --- Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 13:19:03 -0000 1.65 +++ Source/CTest/cmCTestUpdateHandler.cxx 23 Apr 2009 10:21:28 -0000 @@ -30,6 +30,7 @@ #include "cmCTestVC.h" #include "cmCTestCVS.h" #include "cmCTestSVN.h" +#include "cmCTestBZR.h" #include "cmCTestGIT.h" #include <cmsys/auto_ptr.hxx> @@ -52,6 +53,7 @@ "Unknown", "CVS", "SVN", + "BZR", "GIT" }; @@ -135,6 +137,10 @@ { 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; @@ -153,6 +159,10 @@ { 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; @@ -214,6 +224,7 @@ { case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; + case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } @@ -349,6 +360,12 @@ return cmCTestUpdateHandler::e_CVS; } sourceDirectory = dir; + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } + sourceDirectory = dir; sourceDirectory += "/.git"; if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) { @@ -381,6 +398,7 @@ { case e_CVS: key = "CVSCommand"; break; case e_SVN: key = "SVNCommand"; break; + case e_BZR: key = "BZRCommand"; break; case e_GIT: key = "GITCommand"; break; default: break; } Index: Source/CTest/cmCTestUpdateHandler.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v retrieving revision 1.12 diff -u -r1.12 cmCTestUpdateHandler.h --- Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 13:19:04 -0000 1.12 +++ Source/CTest/cmCTestUpdateHandler.h 23 Apr 2009 10:21:28 -0000 @@ -46,6 +46,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_GIT, e_LAST }; Index: Tests/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Tests/CMakeLists.txt,v retrieving revision 1.85 diff -u -r1.85 CMakeLists.txt --- Tests/CMakeLists.txt 22 Apr 2009 13:19:05 -0000 1.85 +++ Tests/CMakeLists.txt 23 Apr 2009 10:21:30 -0000 @@ -925,6 +925,24 @@ LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateCVS_DIR}") ENDIF(CTEST_TEST_UPDATE_CVS AND CVS_FOUND) + # Test CTest Update with BZR + FIND_PROGRAM(BZR_EXECUTABLE NAMES bzr) + MARK_AS_ADVANCED(BZR_EXECUTABLE) + IF(BZR_EXECUTABLE) + # Check if xmloutput plugin is there + EXECUTE_PROCESS(COMMAND ${BZR_EXECUTABLE} xmlplugins RESULT_VARIABLE xmlplugres + OUTPUT_QUIET ERROR_QUIET) + IF( NOT ${xmlplugres} ) + SET(CTestUpdateBZR_DIR "CTest UpdateBZR") + CONFIGURE_FILE("${CMake_SOURCE_DIR}/Tests/CTestUpdateBZR.cmake.in" + "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" @ONLY) + ADD_TEST(CTest.UpdateBZR ${CMAKE_CMAKE_COMMAND} + -P "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" + ) + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateBZR_DIR}") + ENDIF( NOT ${xmlplugres} ) + ENDIF(BZR_EXECUTABLE) + # Test CTest Update with GIT FIND_PROGRAM(GIT_EXECUTABLE NAMES git) MARK_AS_ADVANCED(GIT_EXECUTABLE) Index: Tests/CTestUpdateBZR.cmake.in =================================================================== RCS file: Tests/CTestUpdateBZR.cmake.in diff -N Tests/CTestUpdateBZR.cmake.in --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Tests/CTestUpdateBZR.cmake.in 23 Apr 2009 10:21:30 -0000 @@ -0,0 +1,146 @@ +# This script drives creation of a bzr repository and checks +# that CTest can update from it. + +#----------------------------------------------------------------------------- +# Test in a directory next to this script. +get_filename_component(TOP "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(TOP "${TOP}/@CTestUpdateBZR_DIR@") + +# Include code common to all update tests. +include("@CMAKE_CURRENT_SOURCE_DIR@/CTestUpdateCommon.cmake") + +#----------------------------------------------------------------------------- +# Report bzr tools in use. +message("Using BZR tools:") +set(BZR "@BZR_EXECUTABLE@") +message(" bzr = ${BZR}") + +#----------------------------------------------------------------------------- +# Initialize the testing directory. +message("Creating test directory...") +init_testing() + +#----------------------------------------------------------------------------- +# Create the repository. +message("Creating repository...") +file(MAKE_DIRECTORY ${TOP}/repo.bzr) +run_child( + WORKING_DIRECTORY ${TOP}/repo.bzr + COMMAND ${BZR} init + ) +set(REPO file://${TOP}/repo.bzr) + +#----------------------------------------------------------------------------- +# Import initial content into the repository. +message("Importing content...") +create_content(import) + +# Import the content into the repository. +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} init + ) + +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} add . + ) +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} commit -m "Initial content" + ) +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} push --create-prefix "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Create a working tree. +message("Checking out revision 1...") +run_child( + WORKING_DIRECTORY ${TOP} + COMMAND ${BZR} branch "${REPO}" user-source + ) + +#----------------------------------------------------------------------------- +# Make changes in the working tree. +message("Changing content...") +update_content(user-source files_added files_removed dirs_added) +if(dirs_added) + run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} add ${dirs_added} + ) +endif(dirs_added) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} add ${files_added} + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} rm ${files_removed} + ) + +#----------------------------------------------------------------------------- +# Commit the changes to the repository. +message("Committing revision 2...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} commit -m "Changed content" + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} push "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Make changes in the working tree. +message("Changing content again...") +change_content(user-source) + +#----------------------------------------------------------------------------- +# Commit the changes to the repository. +message("Committing revision 3...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} commit -m "Changed content again" + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} push "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Go back to before the changes so we can test updating. +message("Backing up to revision 1...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} pull --overwrite -r1 + ) + +# Create a modified file. +modify_content(user-source) + +#----------------------------------------------------------------------------- +# Test updating the user work directory with the command-line interface. +message("Running CTest Dashboard Command Line...") + +# Create the user build tree. +create_build_tree(user-source user-binary) +file(APPEND ${TOP}/user-binary/CTestConfiguration.ini + "# BZR command configuration +UpdateCommand: ${BZR} +") + +# Run the dashboard command line interface. +run_dashboard_command_line(user-binary) + +#----------------------------------------------------------------------------- +# Test initial checkout and update with a dashboard script. +message("Running CTest Dashboard Script...") + +create_dashboard_script(dashboard.cmake + "# bzr command configuration +set(CTEST_BZR_COMMAND \"${BZR}\") +set(CTEST_CHECKOUT_COMMAND + \"\\\"\${CTEST_BZR_COMMAND}\\\" branch -r1 \\\"${REPO}\\\" dash-source\") +") + +# Run the dashboard script with CTest. +run_dashboard_script(dashboard.cmake) ![]() Index: Modules/CTest.cmake =================================================================== RCS file: /cvsroot/CMake/CMake/Modules/CTest.cmake,v retrieving revision 1.15 diff -u -r1.15 CTest.cmake --- Modules/CTest.cmake 10 May 2009 10:01:59 -0000 1.15 +++ Modules/CTest.cmake 14 May 2009 17:31:31 -0000 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -130,6 +135,11 @@ IF("${_update_type}" STREQUAL "svn") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE("${_update_type}" STREQUAL "svn") + IF("${_update_type}" STREQUAL "bzr") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF("${_update_type}" STREQUAL "bzr") ENDIF("${_update_type}" STREQUAL "svn") ENDIF("${_update_type}" STREQUAL "cvs") @@ -207,8 +217,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE Index: Source/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CMakeLists.txt,v retrieving revision 1.415 diff -u -r1.415 CMakeLists.txt --- Source/CMakeLists.txt 22 Apr 2009 13:18:57 -0000 1.415 +++ Source/CMakeLists.txt 14 May 2009 17:31:31 -0000 @@ -361,6 +361,8 @@ CTest/cmCTestCVS.h CTest/cmCTestSVN.cxx CTest/cmCTestSVN.h + CTest/cmCTestBZR.cxx + CTest/cmCTestBZR.h CTest/cmCTestGIT.cxx CTest/cmCTestGIT.h ) Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: Source/CTest/cmCTestBZR.cxx diff -N Source/CTest/cmCTestBZR.cxx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.cxx 14 May 2009 17:31:31 -0000 @@ -0,0 +1,732 @@ +#include "cmCTestBZR.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + +#include <cm_expat.h> + +//---------------------------------------------------------------------------- +extern "C" +{ +int cmBZRXMLParserUnknownEncodingHandler(void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info) +{ + static const int latin1[]= + { + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0005, + 0x0006, + 0x0007, + 0x0008, + 0x0009, + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x000E, + 0x000F, + 0x0010, + 0x0011, + 0x0012, + 0x0013, + 0x0014, + 0x0015, + 0x0016, + 0x0017, + 0x0018, + 0x0019, + 0x001A, + 0x001B, + 0x001C, + 0x001D, + 0x001E, + 0x001F, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x20AC, + 0x0081, + 0x201A, + 0x0192, + 0x201E, + 0x2026, + 0x2020, + 0x2021, + 0x02C6, + 0x2030, + 0x0160, + 0x2039, + 0x0152, + 0x008D, + 0x017D, + 0x008F, + 0x0090, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, + 0x2013, + 0x2014, + 0x02DC, + 0x2122, + 0x0161, + 0x203A, + 0x0153, + 0x009D, + 0x017E, + 0x0178, + 0x00A0, + 0x00A1, + 0x00A2, + 0x00A3, + 0x00A4, + 0x00A5, + 0x00A6, + 0x00A7, + 0x00A8, + 0x00A9, + 0x00AA, + 0x00AB, + 0x00AC, + 0x00AD, + 0x00AE, + 0x00AF, + 0x00B0, + 0x00B1, + 0x00B2, + 0x00B3, + 0x00B4, + 0x00B5, + 0x00B6, + 0x00B7, + 0x00B8, + 0x00B9, + 0x00BA, + 0x00BB, + 0x00BC, + 0x00BD, + 0x00BE, + 0x00BF, + 0x00C0, + 0x00C1, + 0x00C2, + 0x00C3, + 0x00C4, + 0x00C5, + 0x00C6, + 0x00C7, + 0x00C8, + 0x00C9, + 0x00CA, + 0x00CB, + 0x00CC, + 0x00CD, + 0x00CE, + 0x00CF, + 0x00D0, + 0x00D1, + 0x00D2, + 0x00D3, + 0x00D4, + 0x00D5, + 0x00D6, + 0x00D7, + 0x00D8, + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x00DD, + 0x00DE, + 0x00DF, + 0x00E0, + 0x00E1, + 0x00E2, + 0x00E3, + 0x00E4, + 0x00E5, + 0x00E6, + 0x00E7, + 0x00E8, + 0x00E9, + 0x00EA, + 0x00EB, + 0x00EC, + 0x00ED, + 0x00EE, + 0x00EF, + 0x00F0, + 0x00F1, + 0x00F2, + 0x00F3, + 0x00F4, + 0x00F5, + 0x00F6, + 0x00F7, + 0x00F8, + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x00FD, + 0x00FE, + 0x00FF + }; + + if ( name==std::string("ascii") || name==std::string("cp1252") ) + { + for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; + return 1; + } + + return 0; +} +} + +//---------------------------------------------------------------------------- +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): + cmCTestGlobalVC(ct, log) +{ + this->PriorRev = this->Unknown; + // Even though it is specified in the documention, with bzr 1.13 + // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. + // Since it doesn't hurt, we specify this environment variable. + cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); + // bzr 1.14.1 on linux 64 bits crashes when the locale is set to C + // So let's make sure it doesn't crash by explicitely putting it to en_EN + cmSystemTools::PutEnv("LC_ALL=en_EN"); +} + +//---------------------------------------------------------------------------- +cmCTestBZR::~cmCTestBZR() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +{ +public: + InfoParser(cmCTestBZR* bzr, const char* prefix): + BZR(bzr), CheckOutFound(false) + { + this->SetLog(&bzr->Log, prefix); + this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); + this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); + } +private: + cmCTestBZR* BZR; + bool CheckOutFound; + cmsys::RegularExpression RegexCheckOut; + cmsys::RegularExpression RegexParent; + virtual bool ProcessLine() + { + if(this->RegexCheckOut.find(this->Line)) + { + this->BZR->URL = this->RegexCheckOut.match(1); + CheckOutFound = true; + } + else if(!CheckOutFound && this->RegexParent.find(this->Line)) + { + this->BZR->URL = this->RegexParent.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +{ +public: + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): + BZR(bzr), Rev(rev) + { + this->SetLog(&bzr->Log, prefix); + this->RegexRevno.compile("^([0-9]+)$"); + } +private: + cmCTestBZR* BZR; + std::string& Rev; + cmsys::RegularExpression RegexRevno; + virtual bool ProcessLine() + { + if(this->RegexRevno.find(this->Line)) + { + this->Rev = this->RegexRevno.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::LoadInfo() +{ + // Run "bzr info" to get the repository info from the work tree. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_info[] = {bzr, "info", 0}; + InfoParser iout(this, "info-out> "); + OutputLogger ierr(this->Log, "info-err> "); + this->RunChild(bzr_info, &iout, &ierr); + + // Run "bzr revno" to get the repository revision number from the work tree. + const char* bzr_revno[] = {bzr, "revno", 0}; + std::string rev; + RevnoParser rout(this, "revno-out> ", rev); + OutputLogger rerr(this->Log, "revno-err> "); + this->RunChild(bzr_revno, &rout, &rerr); + + return rev; +} + +void cmCTestBZR::NoteOldRevision() +{ + this->OldRevision = this->LoadInfo(); + this->Log << "Revision before update: " << this->OldRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::NoteNewRevision() +{ + this->NewRevision = this->LoadInfo(); + this->Log << "Revision after update: " << this->NewRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + this->Log << "URL = " << this->URL << "\n"; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), + EmailRegex("(.*) <([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } +private: + cmCTestBZR* BZR; + + typedef cmCTestBZR::Revision Revision; + typedef cmCTestBZR::Change Change; + Revision Rev; + std::vector<Change> Changes; + Change CurChange; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + this->Rev = Revision(); + this->Changes.clear(); + } + // affected-files can contain blocks of + // modified, unknown, renamed, kind-changed, removed, conflicts, added + else if(strcmp(name, "modified") == 0 + || strcmp(name, "renamed") == 0 + || strcmp(name, "kind-changed") == 0) + { + this->CurChange = Change(); + this->CurChange.Action = 'M'; + } + else if(strcmp(name, "added") == 0) + { + this->CurChange = Change(); + this->CurChange = 'A'; + } + else if(strcmp(name, "removed") == 0) + { + this->CurChange = Change(); + this->CurChange = 'D'; + } + else if(strcmp(name, "unknown") == 0 + || strcmp(name, "conflicts") == 0) + { + // Should not happen here + this->CurChange = Change(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + this->BZR->DoRevision(this->Rev, this->Changes); + } + else if((strcmp(name, "file") == 0 || strcmp(name, "directory") == 0) + && !this->CData.empty()) + { + this->CurChange.Path.assign(&this->CData[0], this->CData.size()); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "symlink") == 0 && !this->CData.empty()) + { + // symlinks have an arobase at the end in the log + this->CurChange.Path.assign(&this->CData[0], this->CData.size()-1); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexUpdate; + + virtual bool ProcessChunk(const char* first, int length) + { + bool last_is_new_line = (*first == '\r' || *first == '\n'); + + const char* const last = first + length; + for(const char* c = first; c != last; ++c) + { + if(*c == '\r' || *c == '\n') + { + if(!last_is_new_line) + { + // Log this line. + if(this->Log && this->Prefix) + { + *this->Log << this->Prefix << this->Line << "\n"; + } + + // Hand this line to the subclass implementation. + if(!this->ProcessLine()) + { + this->Line = ""; + return false; + } + + this->Line = ""; + last_is_new_line = true; + } + } + else + { + // Append this character to the line under construction. + this->Line.append(1, *c); + last_is_new_line = false; + } + } + return true; + } + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)[0], + this->RegexUpdate.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + const std::string dir = cmSystemTools::GetFilenamePath(path); + const std::string name = cmSystemTools::GetFilenameName(path); + + if ( c0=='C' ) + { + this->BZR->Dirs[dir][name].Status = PathConflicting; + return; + } + + if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) + { + this->BZR->Dirs[dir][name].Status = PathUpdated; + return; + } + } +}; + +//---------------------------------------------------------------------------- +bool cmCTestBZR::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + + // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) + + // Use "bzr pull" to update the working tree. + std::vector<char const*> bzr_update; + bzr_update.push_back(this->CommandLineTool.c_str()); + bzr_update.push_back("pull"); + + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + bzr_update.push_back(ai->c_str()); + } + + bzr_update.push_back(this->URL.c_str()); + + bzr_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + // For some reason bzr uses cerr to display the update status + return this->RunUpdateCommand(&bzr_update[0], &err, &out); + return false; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + this->Revisions.clear(); + std::string revs; + if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) + { + // DoRevision takes care of discarding the information about OldRevision + revs = this->OldRevision + ".." + this->NewRevision; + } + else + { + return; + } + + // Run "bzr log" to get all global revisions of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-v", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)[0], + this->RegexStatus.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + if ( c0=='C' ) + { + this->BZR->DoModification(PathConflicting, path); + return; + } + + if ( c0 == '+' || c0 == 'R' || c0 == 'P' + || c1=='M' || c1=='K' || c1=='N' || c1=='D' + || c2 =='*' ) + { + this->BZR->DoModification(PathModified, path); + return; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadModifications() +{ + // Run "bzr status" which reports local modifications. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_status[] = {bzr, "status", "-SV", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(bzr_status, &out, &err); +} Index: Source/CTest/cmCTestBZR.h =================================================================== RCS file: Source/CTest/cmCTestBZR.h diff -N Source/CTest/cmCTestBZR.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.h 14 May 2009 17:31:31 -0000 @@ -0,0 +1,44 @@ +#ifndef cmCTestBZR_h +#define cmCTestBZR_h + +#include "cmCTestGlobalVC.h" + +/** \class cmCTestBZR + * \brief Interaction with bzr command-line tool + * + */ +class cmCTestBZR: public cmCTestGlobalVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestBZR(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestBZR(); + +private: + // Implement cmCTestVC internal API. + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual bool UpdateImpl(); + + // URL of repository directory checked out in the working tree. + std::string URL; + + std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + // Parsing helper classes. + class InfoParser; + class RevnoParser; + class LogParser; + class UpdateParser; + class StatusParser; + friend class InfoParser; + friend class RevnoParser; + friend class LogParser; + friend class UpdateParser; + friend class StatusParser; +}; + +#endif Index: Source/CTest/cmCTestUpdateCommand.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v retrieving revision 1.16 diff -u -r1.16 cmCTestUpdateCommand.cxx --- Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 13:19:03 -0000 1.16 +++ Source/CTest/cmCTestUpdateCommand.cxx 14 May 2009 17:31:31 -0000 @@ -49,6 +49,10 @@ this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITCommand", "CTEST_GIT_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); Index: Source/CTest/cmCTestUpdateHandler.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v retrieving revision 1.65 diff -u -r1.65 cmCTestUpdateHandler.cxx --- Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 13:19:03 -0000 1.65 +++ Source/CTest/cmCTestUpdateHandler.cxx 14 May 2009 17:31:31 -0000 @@ -30,6 +30,7 @@ #include "cmCTestVC.h" #include "cmCTestCVS.h" #include "cmCTestSVN.h" +#include "cmCTestBZR.h" #include "cmCTestGIT.h" #include <cmsys/auto_ptr.hxx> @@ -52,6 +53,7 @@ "Unknown", "CVS", "SVN", + "BZR", "GIT" }; @@ -135,6 +137,10 @@ { 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; @@ -153,6 +159,10 @@ { 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; @@ -214,6 +224,7 @@ { case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; + case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } @@ -349,6 +360,12 @@ return cmCTestUpdateHandler::e_CVS; } sourceDirectory = dir; + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } + sourceDirectory = dir; sourceDirectory += "/.git"; if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) { @@ -381,6 +398,7 @@ { case e_CVS: key = "CVSCommand"; break; case e_SVN: key = "SVNCommand"; break; + case e_BZR: key = "BZRCommand"; break; case e_GIT: key = "GITCommand"; break; default: break; } Index: Source/CTest/cmCTestUpdateHandler.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v retrieving revision 1.12 diff -u -r1.12 cmCTestUpdateHandler.h --- Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 13:19:04 -0000 1.12 +++ Source/CTest/cmCTestUpdateHandler.h 14 May 2009 17:31:31 -0000 @@ -46,6 +46,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_GIT, e_LAST }; Index: Tests/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Tests/CMakeLists.txt,v retrieving revision 1.85 diff -u -r1.85 CMakeLists.txt --- Tests/CMakeLists.txt 22 Apr 2009 13:19:05 -0000 1.85 +++ Tests/CMakeLists.txt 14 May 2009 17:31:32 -0000 @@ -925,6 +925,24 @@ LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateCVS_DIR}") ENDIF(CTEST_TEST_UPDATE_CVS AND CVS_FOUND) + # Test CTest Update with BZR + FIND_PROGRAM(BZR_EXECUTABLE NAMES bzr) + MARK_AS_ADVANCED(BZR_EXECUTABLE) + IF(BZR_EXECUTABLE) + # Check if xmloutput plugin is there + EXECUTE_PROCESS(COMMAND ${BZR_EXECUTABLE} xmlplugins RESULT_VARIABLE xmlplugres + OUTPUT_QUIET ERROR_QUIET) + IF( NOT ${xmlplugres} ) + SET(CTestUpdateBZR_DIR "CTest UpdateBZR") + CONFIGURE_FILE("${CMake_SOURCE_DIR}/Tests/CTestUpdateBZR.cmake.in" + "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" @ONLY) + ADD_TEST(CTest.UpdateBZR ${CMAKE_CMAKE_COMMAND} + -P "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" + ) + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateBZR_DIR}") + ENDIF( NOT ${xmlplugres} ) + ENDIF(BZR_EXECUTABLE) + # Test CTest Update with GIT FIND_PROGRAM(GIT_EXECUTABLE NAMES git) MARK_AS_ADVANCED(GIT_EXECUTABLE) Index: Tests/CTestUpdateBZR.cmake.in =================================================================== RCS file: Tests/CTestUpdateBZR.cmake.in diff -N Tests/CTestUpdateBZR.cmake.in --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Tests/CTestUpdateBZR.cmake.in 14 May 2009 17:31:32 -0000 @@ -0,0 +1,146 @@ +# This script drives creation of a bzr repository and checks +# that CTest can update from it. + +#----------------------------------------------------------------------------- +# Test in a directory next to this script. +get_filename_component(TOP "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(TOP "${TOP}/@CTestUpdateBZR_DIR@") + +# Include code common to all update tests. +include("@CMAKE_CURRENT_SOURCE_DIR@/CTestUpdateCommon.cmake") + +#----------------------------------------------------------------------------- +# Report bzr tools in use. +message("Using BZR tools:") +set(BZR "@BZR_EXECUTABLE@") +message(" bzr = ${BZR}") + +#----------------------------------------------------------------------------- +# Initialize the testing directory. +message("Creating test directory...") +init_testing() + +#----------------------------------------------------------------------------- +# Create the repository. +message("Creating repository...") +file(MAKE_DIRECTORY ${TOP}/repo.bzr) +run_child( + WORKING_DIRECTORY ${TOP}/repo.bzr + COMMAND ${BZR} init + ) +set(REPO file://${TOP}/repo.bzr) + +#----------------------------------------------------------------------------- +# Import initial content into the repository. +message("Importing content...") +create_content(import) + +# Import the content into the repository. +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} init + ) + +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} add . + ) +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} commit -m "Initial content" + ) +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} push --create-prefix "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Create a working tree. +message("Checking out revision 1...") +run_child( + WORKING_DIRECTORY ${TOP} + COMMAND ${BZR} branch "${REPO}" user-source + ) + +#----------------------------------------------------------------------------- +# Make changes in the working tree. +message("Changing content...") +update_content(user-source files_added files_removed dirs_added) +if(dirs_added) + run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} add ${dirs_added} + ) +endif(dirs_added) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} add ${files_added} + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} rm ${files_removed} + ) + +#----------------------------------------------------------------------------- +# Commit the changes to the repository. +message("Committing revision 2...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} commit -m "Changed content" + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} push "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Make changes in the working tree. +message("Changing content again...") +change_content(user-source) + +#----------------------------------------------------------------------------- +# Commit the changes to the repository. +message("Committing revision 3...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} commit -m "Changed content again" + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} push "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Go back to before the changes so we can test updating. +message("Backing up to revision 1...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} pull --overwrite -r1 + ) + +# Create a modified file. +modify_content(user-source) + +#----------------------------------------------------------------------------- +# Test updating the user work directory with the command-line interface. +message("Running CTest Dashboard Command Line...") + +# Create the user build tree. +create_build_tree(user-source user-binary) +file(APPEND ${TOP}/user-binary/CTestConfiguration.ini + "# BZR command configuration +UpdateCommand: ${BZR} +") + +# Run the dashboard command line interface. +run_dashboard_command_line(user-binary) + +#----------------------------------------------------------------------------- +# Test initial checkout and update with a dashboard script. +message("Running CTest Dashboard Script...") + +create_dashboard_script(dashboard.cmake + "# bzr command configuration +set(CTEST_BZR_COMMAND \"${BZR}\") +set(CTEST_CHECKOUT_COMMAND + \"\\\"\${CTEST_BZR_COMMAND}\\\" branch -r1 \\\"${REPO}\\\" dash-source\") +") + +# Run the dashboard script with CTest. +run_dashboard_script(dashboard.cmake) ![]() Index: Modules/CTest.cmake =================================================================== RCS file: /cvsroot/CMake/CMake/Modules/CTest.cmake,v retrieving revision 1.15 diff -u -r1.15 CTest.cmake --- Modules/CTest.cmake 10 May 2009 10:01:59 -0000 1.15 +++ Modules/CTest.cmake 14 May 2009 17:34:43 -0000 @@ -104,6 +104,7 @@ SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") FIND_PROGRAM(SVNCOMMAND svn) + FIND_PROGRAM(BZRCOMMAND bzr) IF(NOT UPDATE_TYPE) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") @@ -111,6 +112,10 @@ ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") SET(UPDATE_TYPE svn) + ELSE(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") + IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") + SET(UPDATE_TYPE bzr) + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.bzr") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn") ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CVS") ENDIF(NOT UPDATE_TYPE) @@ -130,6 +135,11 @@ IF("${_update_type}" STREQUAL "svn") SET(UPDATE_COMMAND "${SVNCOMMAND}") SET(UPDATE_OPTIONS "${SVN_UPDATE_OPTIONS}") + ELSE("${_update_type}" STREQUAL "svn") + IF("${_update_type}" STREQUAL "bzr") + SET(UPDATE_COMMAND "${BZRCOMMAND}") + SET(UPDATE_OPTIONS "${BZR_UPDATE_OPTIONS}") + ENDIF("${_update_type}" STREQUAL "bzr") ENDIF("${_update_type}" STREQUAL "svn") ENDIF("${_update_type}" STREQUAL "cvs") @@ -207,8 +217,10 @@ COVERAGE_COMMAND CVSCOMMAND SVNCOMMAND + BZRCOMMAND CVS_UPDATE_OPTIONS SVN_UPDATE_OPTIONS + BZR_UPDATE_OPTIONS MAKECOMMAND MEMORYCHECK_COMMAND MEMORYCHECK_SUPPRESSIONS_FILE Index: Source/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CMakeLists.txt,v retrieving revision 1.415 diff -u -r1.415 CMakeLists.txt --- Source/CMakeLists.txt 22 Apr 2009 13:18:57 -0000 1.415 +++ Source/CMakeLists.txt 14 May 2009 17:34:43 -0000 @@ -361,6 +361,8 @@ CTest/cmCTestCVS.h CTest/cmCTestSVN.cxx CTest/cmCTestSVN.h + CTest/cmCTestBZR.cxx + CTest/cmCTestBZR.h CTest/cmCTestGIT.cxx CTest/cmCTestGIT.h ) Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: Source/CTest/cmCTestBZR.cxx diff -N Source/CTest/cmCTestBZR.cxx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.cxx 14 May 2009 17:34:43 -0000 @@ -0,0 +1,836 @@ +#include "cmCTestBZR.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLSafe.h" + +#include <cmsys/RegularExpression.hxx> + +#include <cm_expat.h> + +//---------------------------------------------------------------------------- +extern "C" +{ +int cmBZRXMLParserUnknownEncodingHandler(void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info) +{ + static const int latin1[]= + { + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0005, + 0x0006, + 0x0007, + 0x0008, + 0x0009, + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x000E, + 0x000F, + 0x0010, + 0x0011, + 0x0012, + 0x0013, + 0x0014, + 0x0015, + 0x0016, + 0x0017, + 0x0018, + 0x0019, + 0x001A, + 0x001B, + 0x001C, + 0x001D, + 0x001E, + 0x001F, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x20AC, + 0x0081, + 0x201A, + 0x0192, + 0x201E, + 0x2026, + 0x2020, + 0x2021, + 0x02C6, + 0x2030, + 0x0160, + 0x2039, + 0x0152, + 0x008D, + 0x017D, + 0x008F, + 0x0090, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, + 0x2013, + 0x2014, + 0x02DC, + 0x2122, + 0x0161, + 0x203A, + 0x0153, + 0x009D, + 0x017E, + 0x0178, + 0x00A0, + 0x00A1, + 0x00A2, + 0x00A3, + 0x00A4, + 0x00A5, + 0x00A6, + 0x00A7, + 0x00A8, + 0x00A9, + 0x00AA, + 0x00AB, + 0x00AC, + 0x00AD, + 0x00AE, + 0x00AF, + 0x00B0, + 0x00B1, + 0x00B2, + 0x00B3, + 0x00B4, + 0x00B5, + 0x00B6, + 0x00B7, + 0x00B8, + 0x00B9, + 0x00BA, + 0x00BB, + 0x00BC, + 0x00BD, + 0x00BE, + 0x00BF, + 0x00C0, + 0x00C1, + 0x00C2, + 0x00C3, + 0x00C4, + 0x00C5, + 0x00C6, + 0x00C7, + 0x00C8, + 0x00C9, + 0x00CA, + 0x00CB, + 0x00CC, + 0x00CD, + 0x00CE, + 0x00CF, + 0x00D0, + 0x00D1, + 0x00D2, + 0x00D3, + 0x00D4, + 0x00D5, + 0x00D6, + 0x00D7, + 0x00D8, + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x00DD, + 0x00DE, + 0x00DF, + 0x00E0, + 0x00E1, + 0x00E2, + 0x00E3, + 0x00E4, + 0x00E5, + 0x00E6, + 0x00E7, + 0x00E8, + 0x00E9, + 0x00EA, + 0x00EB, + 0x00EC, + 0x00ED, + 0x00EE, + 0x00EF, + 0x00F0, + 0x00F1, + 0x00F2, + 0x00F3, + 0x00F4, + 0x00F5, + 0x00F6, + 0x00F7, + 0x00F8, + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x00FD, + 0x00FE, + 0x00FF + }; + + if ( name==std::string("ascii") || name==std::string("cp1252") ) + { + for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; + return 1; + } + + return 0; +} +} + +//---------------------------------------------------------------------------- +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): + cmCTestGlobalVC(ct, log) +{ + this->PriorRev = this->Unknown; + // Even though it is specified in the documention, with bzr 1.13 + // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. + // Since it doesn't hurt, we specify this environment variable. + cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); + // bzr 1.14.1 on linux 64 bits crashes when the locale is set to C + // So let's make sure it doesn't crash by explicitely putting it to en_EN + cmSystemTools::PutEnv("LC_ALL=en_EN"); +} + +//---------------------------------------------------------------------------- +cmCTestBZR::~cmCTestBZR() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +{ +public: + InfoParser(cmCTestBZR* bzr, const char* prefix): + BZR(bzr), CheckOutFound(false) + { + this->SetLog(&bzr->Log, prefix); + this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); + this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); + } +private: + cmCTestBZR* BZR; + bool CheckOutFound; + cmsys::RegularExpression RegexCheckOut; + cmsys::RegularExpression RegexParent; + virtual bool ProcessLine() + { + if(this->RegexCheckOut.find(this->Line)) + { + this->BZR->URL = this->RegexCheckOut.match(1); + CheckOutFound = true; + } + else if(!CheckOutFound && this->RegexParent.find(this->Line)) + { + this->BZR->URL = this->RegexParent.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +{ +public: + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): + BZR(bzr), Rev(rev) + { + this->SetLog(&bzr->Log, prefix); + this->RegexRevno.compile("^([0-9]+)$"); + } +private: + cmCTestBZR* BZR; + std::string& Rev; + cmsys::RegularExpression RegexRevno; + virtual bool ProcessLine() + { + if(this->RegexRevno.find(this->Line)) + { + this->Rev = this->RegexRevno.match(1); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::LoadInfo() +{ + // Run "bzr info" to get the repository info from the work tree. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_info[] = {bzr, "info", 0}; + InfoParser iout(this, "info-out> "); + OutputLogger ierr(this->Log, "info-err> "); + this->RunChild(bzr_info, &iout, &ierr); + + // Run "bzr revno" to get the repository revision number from the work tree. + const char* bzr_revno[] = {bzr, "revno", 0}; + std::string rev; + RevnoParser rout(this, "revno-out> ", rev); + OutputLogger rerr(this->Log, "revno-err> "); + this->RunChild(bzr_revno, &rout, &rerr); + + return rev; +} + +void cmCTestBZR::NoteOldRevision() +{ + this->OldRevision = this->LoadInfo(); + this->Log << "Revision before update: " << this->OldRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::NoteNewRevision() +{ + this->NewRevision = this->LoadInfo(); + this->Log << "Revision after update: " << this->NewRevision << "\n"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + this->Log << "URL = " << this->URL << "\n"; +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestBZR* bzr, const char* prefix): + OutputLogger(bzr->Log, prefix), BZR(bzr), + EmailRegex("(.*) <([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } + + virtual int InitializeParser() + { + int res = cmXMLParser::InitializeParser(); + if (res) + { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, 0); + } + return res; + } +private: + cmCTestBZR* BZR; + + typedef cmCTestBZR::Revision Revision; + typedef cmCTestBZR::Change Change; + Revision Rev; + std::vector<Change> Changes; + Change CurChange; + std::vector<char> CData; + + cmsys::RegularExpression EmailRegex; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "log") == 0) + { + this->Rev = Revision(); + this->Changes.clear(); + } + // affected-files can contain blocks of + // modified, unknown, renamed, kind-changed, removed, conflicts, added + else if(strcmp(name, "modified") == 0 + || strcmp(name, "renamed") == 0 + || strcmp(name, "kind-changed") == 0) + { + this->CurChange = Change(); + this->CurChange.Action = 'M'; + } + else if(strcmp(name, "added") == 0) + { + this->CurChange = Change(); + this->CurChange = 'A'; + } + else if(strcmp(name, "removed") == 0) + { + this->CurChange = Change(); + this->CurChange = 'D'; + } + else if(strcmp(name, "unknown") == 0 + || strcmp(name, "conflicts") == 0) + { + // Should not happen here + this->CurChange = Change(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "log") == 0) + { + this->BZR->DoRevision(this->Rev, this->Changes); + } + else if((strcmp(name, "file") == 0 || strcmp(name, "directory") == 0) + && !this->CData.empty()) + { + this->CurChange.Path.assign(&this->CData[0], this->CData.size()); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "symlink") == 0 && !this->CData.empty()) + { + // symlinks have an arobase at the end in the log + this->CurChange.Path.assign(&this->CData[0], this->CData.size()-1); + this->Changes.push_back(this->CurChange); + } + else if(strcmp(name, "committer") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + if(this->EmailRegex.find(this->Rev.Author)) + { + this->Rev.Author = this->EmailRegex.match(1); + //email = email_regex.match(2); + } + } + else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "message") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "revno") == 0 && !this->CData.empty()) + { + this->Rev.Rev.assign(&this->CData[0], this->CData.size()); + } + this->CData.clear(); + } + + virtual void ReportError(int, int, const char* msg) + { + this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +{ +public: + UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexUpdate; + + virtual bool ProcessChunk(const char* first, int length) + { + bool last_is_new_line = (*first == '\r' || *first == '\n'); + + const char* const last = first + length; + for(const char* c = first; c != last; ++c) + { + if(*c == '\r' || *c == '\n') + { + if(!last_is_new_line) + { + // Log this line. + if(this->Log && this->Prefix) + { + *this->Log << this->Prefix << this->Line << "\n"; + } + + // Hand this line to the subclass implementation. + if(!this->ProcessLine()) + { + this->Line = ""; + return false; + } + + this->Line = ""; + last_is_new_line = true; + } + } + else + { + // Append this character to the line under construction. + this->Line.append(1, *c); + last_is_new_line = false; + } + } + return true; + } + + bool ProcessLine() + { + if(this->RegexUpdate.find(this->Line)) + { + this->DoPath(this->RegexUpdate.match(1)[0], + this->RegexUpdate.match(2)[0], + this->RegexUpdate.match(3)[0], + this->RegexUpdate.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + const std::string dir = cmSystemTools::GetFilenamePath(path); + const std::string name = cmSystemTools::GetFilenameName(path); + + if ( c0=='C' ) + { + this->BZR->Dirs[dir][name].Status = PathConflicting; + return; + } + + if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) + { + this->BZR->Dirs[dir][name].Status = PathUpdated; + return; + } + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestBZR::GetNightlyOrLatestRevno() +{ + // Parse "bzr log -r curr_rev.. --xml bzr_url" to get a list of all revision numbers + std::string revs = this->OldRevision + ".."; + + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log4revno-out> "); + OutputLogger err(this->Log, "log4revno-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + + // In case no revision occured we keep the old revision + if(this->Revisions.empty()) return this->OldRevision; + + // Get time for nightly build (even though we don't always use it) + // This is in UTC time + struct tm* t = this->CTest->GetNightlyTime( + this->CTest->GetCTestConfiguration("NightlyStartTime"), + this->CTest->GetTomorrowTag()); + + if (t->tm_isdst != 0) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Daylight saving time is used in the result of GetNightlyTime which is supposed to use UTC time. Results might not be quite accurate." + << std::endl); + } + + // To be consistent with UTC time we should not use mktime on UTC-defined + // tm structs. It is OK to do so here since we only need to do some + // ordering comparisons on timestamps. + // A better solution would be to use timegm instead of mktime but + // timegm is not a widely supported function + const time_t nightly_timestamp = mktime(t); + + const bool is_nightly = this->CTest->GetTestModel() == cmCTest::NIGHTLY; + + // If we don't find anything, by default the new revision is the old one + std::string new_revision = this->OldRevision; + + for(std::list<Revision>::const_iterator ri = this->Revisions.begin(); + ri != this->Revisions.end(); ++ri) + { + if (!is_nightly) + { + if(atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + { + new_revision = ri->Rev; + } + } + else + { + // Parse the time of this revision + // curl_getdate does not parse "Tue 2009-04-14 15:36:32 +0200" + // strptime is not available on msvc and it is not clear to me how it handles timezones + // This means we need a workaround + const std::string & date = ri->Date; + struct tm timeinfo; + + cmsys::RegularExpression regex_date( + ".* ([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) ([-+])([0-9][0-9])([0-9][0-9])"); + if (!regex_date.find(date)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to parse bzr commit time: " << date.c_str() + << std::endl); + continue; + } + + // Construct a UTC tm + timeinfo.tm_year = atoi(regex_date.match(1).c_str()) - 1900; + timeinfo.tm_mon = atoi(regex_date.match(2).c_str()) - 1; + timeinfo.tm_mday = atoi(regex_date.match(3).c_str()); + timeinfo.tm_hour = atoi(regex_date.match(4).c_str()); + timeinfo.tm_min = atoi(regex_date.match(5).c_str()); + timeinfo.tm_sec = atoi(regex_date.match(6).c_str()); + + timeinfo.tm_isdst = 0; + // timeinfo.tm_wday; // Should not be used + // timeinfo.tm_yday; // Should not be used + + time_t offset = atoi(regex_date.match(8).c_str())*3600 + + atoi(regex_date.match(9).c_str())*60; + if (regex_date.match(7)=="-") offset = - offset; + + // See previous comment. A nicer solution would be to use timegm + // instead of mktime but this should not impact the results. + const time_t commit_timestamp = mktime(&timeinfo)-offset; + + // Keep it if it is the highest revision number prior to the nightly time + if((atoi(ri->Rev.c_str()) > atoi(new_revision.c_str())) + && (commit_timestamp <= nightly_timestamp)) + { + new_revision = ri->Rev; + } + } + } + + return new_revision; +} + +//---------------------------------------------------------------------------- +bool cmCTestBZR::UpdateImpl() +{ + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + + // Get the revision number used to update the repository + std::string new_revision = this->GetNightlyOrLatestRevno(); + + std::vector<char const*> bzr_update; + bzr_update.push_back(this->CommandLineTool.c_str()); + bzr_update.push_back("pull"); + bzr_update.push_back("-r"); + bzr_update.push_back(new_revision.c_str()); + bzr_update.push_back(this->URL.c_str()); + + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + bzr_update.push_back(ai->c_str()); + } + bzr_update.push_back(0); + + UpdateParser out(this, "up-out> "); + OutputLogger err(this->Log, "up-err> "); + // For some reason bzr uses cerr to display the update status + return this->RunUpdateCommand(&bzr_update[0], &err, &out); + return false; +} + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadRevisions() +{ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Gathering version information (one . per revision):\n" + " " << std::flush); + + // We are interested in every revision included in the update. + this->Revisions.clear(); + std::string revs; + if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) + { + // DoRevision takes care of discarding the information about OldRevision + revs = this->OldRevision + ".." + this->NewRevision; + } + else + { + return; + } + + // Run "bzr log" to get all global revisions of interest. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_log[] = {bzr, "log", "-v", "-r", revs.c_str(), "--xml", this->URL.c_str(), 0}; + { + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); +} + +//---------------------------------------------------------------------------- +class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) + { + this->SetLog(&bzr->Log, prefix); + this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); + } +private: + cmCTestBZR* BZR; + cmsys::RegularExpression RegexStatus; + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)[0], + this->RegexStatus.match(3)[0], + this->RegexStatus.match(4)); + } + return true; + } + + void DoPath(char c0, char c1, char c2, std::string const& path) + { + if(path.empty()) return; + + if ( c0=='C' ) + { + this->BZR->DoModification(PathConflicting, path); + return; + } + + if ( c0 == '+' || c0 == 'R' || c0 == 'P' + || c1=='M' || c1=='K' || c1=='N' || c1=='D' + || c2 =='*' ) + { + this->BZR->DoModification(PathModified, path); + return; + } + } +}; + +//---------------------------------------------------------------------------- +void cmCTestBZR::LoadModifications() +{ + // Run "bzr status" which reports local modifications. + const char* bzr = this->CommandLineTool.c_str(); + const char* bzr_status[] = {bzr, "status", "-SV", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(bzr_status, &out, &err); +} Index: Source/CTest/cmCTestBZR.h =================================================================== RCS file: Source/CTest/cmCTestBZR.h diff -N Source/CTest/cmCTestBZR.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Source/CTest/cmCTestBZR.h 14 May 2009 17:34:43 -0000 @@ -0,0 +1,45 @@ +#ifndef cmCTestBZR_h +#define cmCTestBZR_h + +#include "cmCTestGlobalVC.h" + +/** \class cmCTestBZR + * \brief Interaction with bzr command-line tool + * + */ +class cmCTestBZR: public cmCTestGlobalVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestBZR(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestBZR(); + +private: + // Implement cmCTestVC internal API. + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual std::string GetNightlyOrLatestRevno(); + virtual bool UpdateImpl(); + + // URL of repository directory checked out in the working tree. + std::string URL; + + std::string LoadInfo(); + void LoadModifications(); + void LoadRevisions(); + + // Parsing helper classes. + class InfoParser; + class RevnoParser; + class LogParser; + class UpdateParser; + class StatusParser; + friend class InfoParser; + friend class RevnoParser; + friend class LogParser; + friend class UpdateParser; + friend class StatusParser; +}; + +#endif Index: Source/CTest/cmCTestUpdateCommand.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v retrieving revision 1.16 diff -u -r1.16 cmCTestUpdateCommand.cxx --- Source/CTest/cmCTestUpdateCommand.cxx 22 Apr 2009 13:19:03 -0000 1.16 +++ Source/CTest/cmCTestUpdateCommand.cxx 14 May 2009 17:34:44 -0000 @@ -49,6 +49,10 @@ this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRCommand", "CTEST_BZR_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITCommand", "CTEST_GIT_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); Index: Source/CTest/cmCTestUpdateHandler.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v retrieving revision 1.65 diff -u -r1.65 cmCTestUpdateHandler.cxx --- Source/CTest/cmCTestUpdateHandler.cxx 22 Apr 2009 13:19:03 -0000 1.65 +++ Source/CTest/cmCTestUpdateHandler.cxx 14 May 2009 17:34:44 -0000 @@ -30,6 +30,7 @@ #include "cmCTestVC.h" #include "cmCTestCVS.h" #include "cmCTestSVN.h" +#include "cmCTestBZR.h" #include "cmCTestGIT.h" #include <cmsys/auto_ptr.hxx> @@ -52,6 +53,7 @@ "Unknown", "CVS", "SVN", + "BZR", "GIT" }; @@ -135,6 +137,10 @@ { 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; @@ -153,6 +159,10 @@ { 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; @@ -214,6 +224,7 @@ { case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; + case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } @@ -349,6 +360,12 @@ return cmCTestUpdateHandler::e_CVS; } sourceDirectory = dir; + sourceDirectory += "/.bzr"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_BZR; + } + sourceDirectory = dir; sourceDirectory += "/.git"; if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) { @@ -381,6 +398,7 @@ { case e_CVS: key = "CVSCommand"; break; case e_SVN: key = "SVNCommand"; break; + case e_BZR: key = "BZRCommand"; break; case e_GIT: key = "GITCommand"; break; default: break; } Index: Source/CTest/cmCTestUpdateHandler.h =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v retrieving revision 1.12 diff -u -r1.12 cmCTestUpdateHandler.h --- Source/CTest/cmCTestUpdateHandler.h 22 Apr 2009 13:19:04 -0000 1.12 +++ Source/CTest/cmCTestUpdateHandler.h 14 May 2009 17:34:44 -0000 @@ -46,6 +46,7 @@ e_UNKNOWN = 0, e_CVS, e_SVN, + e_BZR, e_GIT, e_LAST }; Index: Tests/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Tests/CMakeLists.txt,v retrieving revision 1.85 diff -u -r1.85 CMakeLists.txt --- Tests/CMakeLists.txt 22 Apr 2009 13:19:05 -0000 1.85 +++ Tests/CMakeLists.txt 14 May 2009 17:34:44 -0000 @@ -925,6 +925,24 @@ LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateCVS_DIR}") ENDIF(CTEST_TEST_UPDATE_CVS AND CVS_FOUND) + # Test CTest Update with BZR + FIND_PROGRAM(BZR_EXECUTABLE NAMES bzr) + MARK_AS_ADVANCED(BZR_EXECUTABLE) + IF(BZR_EXECUTABLE) + # Check if xmloutput plugin is there + EXECUTE_PROCESS(COMMAND ${BZR_EXECUTABLE} xmlplugins RESULT_VARIABLE xmlplugres + OUTPUT_QUIET ERROR_QUIET) + IF( NOT ${xmlplugres} ) + SET(CTestUpdateBZR_DIR "CTest UpdateBZR") + CONFIGURE_FILE("${CMake_SOURCE_DIR}/Tests/CTestUpdateBZR.cmake.in" + "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" @ONLY) + ADD_TEST(CTest.UpdateBZR ${CMAKE_CMAKE_COMMAND} + -P "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" + ) + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateBZR_DIR}") + ENDIF( NOT ${xmlplugres} ) + ENDIF(BZR_EXECUTABLE) + # Test CTest Update with GIT FIND_PROGRAM(GIT_EXECUTABLE NAMES git) MARK_AS_ADVANCED(GIT_EXECUTABLE) Index: Tests/CTestUpdateBZR.cmake.in =================================================================== RCS file: Tests/CTestUpdateBZR.cmake.in diff -N Tests/CTestUpdateBZR.cmake.in --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Tests/CTestUpdateBZR.cmake.in 14 May 2009 17:34:44 -0000 @@ -0,0 +1,146 @@ +# This script drives creation of a bzr repository and checks +# that CTest can update from it. + +#----------------------------------------------------------------------------- +# Test in a directory next to this script. +get_filename_component(TOP "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(TOP "${TOP}/@CTestUpdateBZR_DIR@") + +# Include code common to all update tests. +include("@CMAKE_CURRENT_SOURCE_DIR@/CTestUpdateCommon.cmake") + +#----------------------------------------------------------------------------- +# Report bzr tools in use. +message("Using BZR tools:") +set(BZR "@BZR_EXECUTABLE@") +message(" bzr = ${BZR}") + +#----------------------------------------------------------------------------- +# Initialize the testing directory. +message("Creating test directory...") +init_testing() + +#----------------------------------------------------------------------------- +# Create the repository. +message("Creating repository...") +file(MAKE_DIRECTORY ${TOP}/repo.bzr) +run_child( + WORKING_DIRECTORY ${TOP}/repo.bzr + COMMAND ${BZR} init + ) +set(REPO file://${TOP}/repo.bzr) + +#----------------------------------------------------------------------------- +# Import initial content into the repository. +message("Importing content...") +create_content(import) + +# Import the content into the repository. +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} init + ) + +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} add . + ) +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} commit -m "Initial content" + ) +run_child(WORKING_DIRECTORY ${TOP}/import + COMMAND ${BZR} push --create-prefix "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Create a working tree. +message("Checking out revision 1...") +run_child( + WORKING_DIRECTORY ${TOP} + COMMAND ${BZR} branch "${REPO}" user-source + ) + +#----------------------------------------------------------------------------- +# Make changes in the working tree. +message("Changing content...") +update_content(user-source files_added files_removed dirs_added) +if(dirs_added) + run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} add ${dirs_added} + ) +endif(dirs_added) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} add ${files_added} + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} rm ${files_removed} + ) + +#----------------------------------------------------------------------------- +# Commit the changes to the repository. +message("Committing revision 2...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} commit -m "Changed content" + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} push "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Make changes in the working tree. +message("Changing content again...") +change_content(user-source) + +#----------------------------------------------------------------------------- +# Commit the changes to the repository. +message("Committing revision 3...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} commit -m "Changed content again" + ) +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} push "${REPO}" + ) + +#----------------------------------------------------------------------------- +# Go back to before the changes so we can test updating. +message("Backing up to revision 1...") +run_child( + WORKING_DIRECTORY ${TOP}/user-source + COMMAND ${BZR} pull --overwrite -r1 + ) + +# Create a modified file. +modify_content(user-source) + +#----------------------------------------------------------------------------- +# Test updating the user work directory with the command-line interface. +message("Running CTest Dashboard Command Line...") + +# Create the user build tree. +create_build_tree(user-source user-binary) +file(APPEND ${TOP}/user-binary/CTestConfiguration.ini + "# BZR command configuration +UpdateCommand: ${BZR} +") + +# Run the dashboard command line interface. +run_dashboard_command_line(user-binary) + +#----------------------------------------------------------------------------- +# Test initial checkout and update with a dashboard script. +message("Running CTest Dashboard Script...") + +create_dashboard_script(dashboard.cmake + "# bzr command configuration +set(CTEST_BZR_COMMAND \"${BZR}\") +set(CTEST_CHECKOUT_COMMAND + \"\\\"\${CTEST_BZR_COMMAND}\\\" branch -r1 \\\"${REPO}\\\" dash-source\") +") + +# Run the dashboard script with CTest. +run_dashboard_script(dashboard.cmake) ![]() Index: Source/CTest/cmCTestBZR.cxx =================================================================== RCS file: /cvsroot/CMake/CMake/Source/CTest/cmCTestBZR.cxx,v retrieving revision 1.1 diff -u -r1.1 cmCTestBZR.cxx --- Source/CTest/cmCTestBZR.cxx 14 May 2009 20:13:51 -0000 1.1 +++ Source/CTest/cmCTestBZR.cxx 15 May 2009 13:39:09 -0000 @@ -67,7 +67,12 @@ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF }; - if ( name==std::string("ascii") || name==std::string("cp1252") ) + // BZR xml output plugin can use some encodings that are not natively + // recognised by the xml parser of ctest. This will lead to an error, + // e.g. "Error parsing bzr log xml: unknown encoding", the following + // is a workaround for these unknown encodings + if ( name==std::string("ascii") || name==std::string("cp1252") + || name==std::string("ANSI_X3.4-1968") ) { for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; return 1; Index: Tests/CMakeLists.txt =================================================================== RCS file: /cvsroot/CMake/CMake/Tests/CMakeLists.txt,v retrieving revision 1.86 diff -u -r1.86 CMakeLists.txt --- Tests/CMakeLists.txt 14 May 2009 20:13:52 -0000 1.86 +++ Tests/CMakeLists.txt 15 May 2009 13:39:09 -0000 @@ -939,6 +939,10 @@ ADD_TEST(CTest.UpdateBZR ${CMAKE_CMAKE_COMMAND} -P "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" ) + ADD_TEST(CTest.UpdateBZR.CLocale ${CMAKE_CMAKE_COMMAND} + -P "${CMake_BINARY_DIR}/Tests/CTestUpdateBZR.cmake" + ) + SET_TESTS_PROPERTIES(CTest.UpdateBZR.CLocale PROPERTIES ENVIRONMENT LC_ALL=C) LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${CTestUpdateBZR_DIR}") ENDIF( NOT ${xmlplugres} ) ENDIF(BZR_EXECUTABLE) | ||||||||
Relationships | |||||||||||||
|
Relationships |
Notes | |
(0012797) Tom Vercauteren (reporter) 2008-07-22 08:45 |
This feature request is related to the issue 6994 (http://www.itk.org/Bug/view.php?id=6994 [^]) |
(0013168) Nicolas Savoire (reporter) 2008-08-25 07:40 |
I have a uploaded a patch (patch_cmake_bzr_support.diff) which adds basic bzr support to ctest. I am no entirely satisified since it duplicates a lot of code of cvs/svn update handling. Moreover I had to resort to workarounds such as the update works the same way as cvs/svn. I suppose the source dir is a branch or a checkout of a master bzr branch (I don't think my modifications handle lightweight checkouts). bzr, through the command pull, supports the update of the local repsoitory to a specific date, nevertheless it doesn't support timezone in dates and the given date is first converted to a revision number which is pulled from the parent branch, since the revision number is computed from the local copy, the wrong revision number is compued, moreover the computed revision number corresponds to the first revision commited AFTER the given date. To work around these issues, first a 'bzr info' command is executed to retrieve the checkout or parent branch (denoted parent_branch), then a 'bzr revno' to get the current revision (denoted cur_rev), then 'bzr log -r cur_rev.. parent_branch' allows to retrieve the log of the parent branch since revison cur_rev and to determine which revision must be pulled accordingly to the given date (let's call this revision today_rev). Then 'bzr pull -r today_rev parent_branch' is called to update the repository to the wanted revision, the output is parsed to build the list of modified files. Finally for each modified file, similarly to svn update handling, 'bzr log -r cur_rev..today_rev --xml file' command is run to retrieve modification infromation for each file. bzr xml-output must be installed (either version 0.4.5 or latest trunk version because version 0.5.0 has a bug which suppresses some needed output). |
(0013171) Brad King (manager) 2008-08-25 09:48 |
I haven't looked at bzr, but I assume it supports global revisions like svn instead of per-file revisions. The main problem with the current CTest update and change-report model is that it was written for CVS's per-file revisions. We need to get some CDash work done to support a new XML spec that has project-wide revisions (atomic commits). Then we plan to overhaul the CTest update stuff to support this more modern update model. |
(0016136) Tom Vercauteren (reporter) 2009-04-22 11:19 |
I have uploaded an initial patch for bzr support that uses the new VCS code in the CVS version of cmake. It basically converts the patch by nicos to the current CMake code. |
(0016137) Brad King (manager) 2009-04-22 11:53 |
Thanks, Tom! Actually just this morning I committed git support. See issue 0006994. Part of the changes refactored cmCTestSVN to split out the parts of it useful to any globally-versioned tool. There is a new class cmCTestGlobalVC from which both cmCTestSVN and cmCTestGIT derive. It should help reduce the size of the code for cmCTestBZR and avoid duplication. Do you mind updating the patch accordingly? |
(0016138) Tom Vercauteren (reporter) 2009-04-22 11:58 |
Actually I am monitoring issue 0006994 which is why I submitted the patch today :) My latest patch (cmake-bzr-2009-04-22.patch) should be up to date. It implements cmCTestBZR that derives from cmCTestGlobalVC. |
(0016139) Tom Vercauteren (reporter) 2009-04-22 12:01 |
Argh sorry, I didn't see the Global in "cmCTestGlobalVC"... Wrong copy-paste So I guess a new version of the patch would be helpful. I'll see if I can get some time (maybe next week) for that. |
(0016141) Tom Vercauteren (reporter) 2009-04-22 12:48 |
Uploaded a new patch (cmake-bzr-2009-04-22-bis.patch) that really makes cmCTestBZR derive from cmCTestGlobalVC and not directly cmCTestVC anymore. I should also mention that this patch requires the bzr xmloutput plugin (e.g. version 0.8.3): https://launchpad.net/bzr-xmloutput [^] |
(0016143) Brad King (manager) 2009-04-22 13:20 |
Thanks. That looks pretty close. We also need a test. Would you also please copy one of these files: Tests/CTestUpdateCVS.cmake.in Tests/CTestUpdateGIT.cmake.in Tests/CTestUpdateSVN.cmake.in to Tests/CTestUpdateBZR.cmake.in and modify it accordingly? These tests create a simple repository and some working trees and check that CTest reports changes correctly. |
(0016151) Tom Vercauteren (reporter) 2009-04-23 06:27 |
Today's patch (cmake-bzr-2009-04-23.patch) adds such a unit test and simplifies the implementation of cmCTestBZR. In the previous implementations, we missed the -v option in bzr log and therefore had to find a workaround to get a correct list of modified files. |
(0016153) Brad King (manager) 2009-04-23 09:03 |
Great, thanks! The test passes for me :) Your simplifications since the previous patches also look good. The remaining issue is the nightly start time support (we can work out strptime if necessary, but there is a deeper problem). Please look at the discussion in issue 0006994. We cannot reliably use the most recent revision that occurs before the start time. Someone could publish new revisions that were committed before the nightly start time and cause different dashboard clients to test different versions. This is a problem for all distributed VCS tools. Some central location must choose and mark a single revision for testing on a given day. Typically this should be the revision published as of the nightly start time. The solution I propose in 0006994 is somewhat git-specific. Do you have any ideas how it might work for bzr? Ideally all information should be passed through the VCS tool protocols, but as a last resort we could use another protocol like http GET. |
(0016157) Tom Vercauteren (reporter) 2009-04-23 10:25 |
I am aware of the limitation you presented in 0006994. The current solution is however sufficiently reliable for our uses which is why we didn't bother adding an additional server-side cron. I think that the git solution you propose could be extended to bzr by using tags, e.g. the server would start by running bzr tag --force ctest-nightly-tag and the clients would simply need to pull this tag bzr pull -r tag:ctest-nightly-tag ${BZR_SERVER_URL} By reusing the same tag name every night (and thus the --force option), the repository won't be "polluted" by a multiplicity of tags. To get a more robust behavior that also works without the server-side cron, the client within cmake could do the following: - call "bzr tags" to check if the "ctest-nightly-tag" tag actually exists - If ctest-nightly-tag" exists, call "bzr log -r tag:ctest-nightly-tag ${BZR_SERVER_URL}" to get the date that corresponds to "ctest-nightly-tag" - If "ctest-nightly-tag" does not exists or is more than 24 hours old cmake would use the behavior proposed in cmake-bzr-2009-04-23.patch. Otherwise, the client would use the above procedure. Thoughts? |
(0016159) Brad King (manager) 2009-04-23 10:54 |
Something like that could work, but the tag would have be branch-specific. Perhaps it is better to have CTest simply update using the commit time heuristic by default, but then define an interface a project can use to override the default update. CTest would still check the work tree revision before and after the update to report changes, but the actual 'pull' operation could be customized with a user script. This would allow each project to do its own thing. What is probably more important is that CTest submits the revision identifier to CDash, and that CDash reports it. It will take a little work on my end but is not too hard. This is not something that affects your patch though. |
(0016160) Brad King (manager) 2009-04-23 10:56 |
I was just reading some bzr documentation and thought of another way around the strptime thing. In order to identify the latest revision before the nightly start time, might this work? bzr log -r $oldrev..before:date:2009-04-22,23:00:00 $url Then we do not need to parse dates at all. |
(0016161) Tom Vercauteren (reporter) 2009-04-23 10:59 |
That makes sense. As a side note, bzr tags are branch specific. The actual branch that needs tagging can be specified by the cron job with the -d option (-d ARG, --directory=ARG Branch in which to place the tag). |
(0016162) Brad King (manager) 2009-04-23 11:01 |
If we want CTest to report the exact version tested for a bzr project, how might we get a unique revision number? Does it have to be a combination of the upstream url and its revision number? Since bzr pull does not work for diverged branches, is it guaranteed that the local and remote revisions match? |
(0016163) Tom Vercauteren (reporter) 2009-04-23 11:10 |
Regarding http://www.cmake.org/Bug/view.php?id=6857#c16160 [^] According to nicos (http://www.cmake.org/Bug/view.php?id=6857#c13168 [^]) "bzr, through the command pull, supports the update of the local repsoitory to a specific date, nevertheless it doesn't support timezone in dates" Things might have changed since then but I didn't recheck... Regarding http://www.cmake.org/Bug/view.php?id=6857#c16162 [^] Yes indeed to be able to get the exact revision that has been used, we need the upstream url and its revision number. The current heuristic is to get the upstream url by parsing the output of "bzr info". Another option would be to have the upstream url passed has a parameter, or use a combination of both: use the parameter if it is defined and fallback to the "bzr info" parsing if it is not provided. |
(0016166) Brad King (manager) 2009-04-23 12:03 |
It looks like the date is transformed using the local time zone. For example, I'm in '-0400'. I run bzr log -r 'date:2009-03-23,11:52:18' http://bazaar-vcs.org/bzr/bzr.dev [^] and the revision I see is dated 'Mon 2009-03-23 15:52:19 +0000' (note my 11 became 15 in the commit timestamp). If I change the time to '11:52:20' I get a different revision. We could probably compute the nightly start time and then print to a string in the local time zone to get the proper date. However, the 'before:date:...' trick will not work if no commit after the nightly time exists. I guess this leads to nicos' conclusion. Regarding the current patch, it seems to use the local OldRevision as the base of the remote log. Might this mismatch with the upstream revisions? |
(0016167) Tom Vercauteren (reporter) 2009-04-23 13:05 |
Right... this approach is far from being bullet-proof. However I don't see a right way to handle such cases. There are many possible failure scenarios: - The local sources are neither a branch or a checkout of a master bzr branch - The local sources are already to a higher revision than the requested nightly revision* - The local sources have local changes that have been locally committed - etc. Anyhow concerning the exact question "Regarding the current patch, it seems to use the local OldRevision as the base of the remote log. Might this mismatch with the upstream revisions?" A mismatch can happen if local changes have been committed locally but then the branches will have diverged and "bzr pull -r ${NewRevision} ${BZR_URL}" will do nothing and exit with an error code. So even if the log might be silently but wrongly interpreted, the update won't happen. Hop this helps. * bzr pull won't downgrade a revision without the --overwrite option but using --overwrite is a VERY dangerous operation since if the local sources are bound to the master branch (i.e. it is a checkout) bzr pull --overwrite to an old revision will also downgrade the master branch. This implies that all commits that happened after the old revision are lost on the server! |
(0016168) Brad King (manager) 2009-04-23 13:35 |
I don't care much about use cases where development (with commits) happens in a tree managed a by CTest dashboard script. Nightly builds should be pristine copies of the upstream, so revision numbers should always match (right?). |
(0016169) Tom Vercauteren (reporter) 2009-04-23 13:46 |
As far as I understand it, yes. If the sources for Nightly builds are pristine copies of the upstream as they should, revision numbers should match. This only requires that the same sources are not used for example for both continuous and nightly builds (due to the --overwrite restriction). By the way, I realized that the time reported by the log command can be requested in UTC time bzr log --xml --timezone utc -v -r ${REV_STR} ${BZR_URL} Note however that this timezone information does not seem to change the way that 'before:date:...' gets interpreted. |
(0016433) Brad King (manager) 2009-05-14 10:14 |
I've thought quite a bit about the nightly start time issue. The long-term solution is probably to change the model that CTest/CDash use to agree on a version to test. Since CTest clients must contact CDash after doing a build to submit results anyway, there is no reason not to contact it before starting to ask what version to test. This will also facilitate possible future 'test-on-demand' features. In the short-term I'd like to commit bzr support without addressing the nightly start time problem at all. This will help get it tested and give it some exposure. Can you please split your patch into two parts? The first part should bring bzr support to where git support is now...just always update to the latest revision from upstream (and a TODO comment for nightly mode). The second part can retain your nightly mode prototype for reference in this issue, but I won't commit it yet. This division also avoids the strptime problem for now. Thanks. |
(0016441) Tom Vercauteren (reporter) 2009-05-14 13:49 |
Sounds great! I have uploaded the updated complete patch as well as the slimmed one (without the nightly part). Although I am confident that the slimmed patch should work, as a disclaimer, I have to mention that I only tested it through the unit test. The complete patch however is used on a daily basis. For reference, I'll post another patch that contains only the nightly revision guessing part once you commit BZR support. It's easier that generating a patch for a patch. Regarding your proposed long-term solution, I makes a lot of sense to me. The only thing is that the transition will be a little more complex since it requires a synchronized move of cmake and cdash. But I guess that this can be sorted out without too much hassle. |
(0016450) Brad King (manager) 2009-05-14 16:00 |
Thanks! This new hunk may be a problem though: + // bzr 1.14.1 on linux 64 bits crashes when the locale is set to C + // So let's make sure it doesn't crash by explicitely putting it to en_EN + cmSystemTools::PutEnv("LC_ALL=en_EN"); The 'en_EN' locale is not guaranteed to be available everywhere. Only 'C' is guaranteed to exist. I'm going to commit it without this part. Can you create a more specific test, please? |
(0016452) Brad King (manager) 2009-05-14 16:14 |
ENH: Teach CTest to handle Bazaar repositories /cvsroot/CMake/CMake/Modules/CTest.cmake,v <-- Modules/CTest.cmake new revision: 1.16; previous revision: 1.15 /cvsroot/CMake/CMake/Source/CMakeLists.txt,v <-- Source/CMakeLists.txt new revision: 1.416; previous revision: 1.415 /cvsroot/CMake/CMake/Source/CTest/cmCTestBZR.cxx,v <-- Source/CTest/cmCTestBZR.cxx initial revision: 1.1 /cvsroot/CMake/CMake/Source/CTest/cmCTestBZR.h,v <-- Source/CTest/cmCTestBZR.h initial revision: 1.1 /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateCommand.cxx,v <-- Source/CTest/cmCTestUpdateCommand.cxx new revision: 1.17; previous revision: 1.16 /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.cxx,v <-- Source/CTest/cmCTestUpdateHandler.cxx new revision: 1.66; previous revision: 1.65 /cvsroot/CMake/CMake/Source/CTest/cmCTestUpdateHandler.h,v <-- Source/CTest/cmCTestUpdateHandler.h new revision: 1.13; previous revision: 1.12 /cvsroot/CMake/CMake/Tests/CMakeLists.txt,v <-- Tests/CMakeLists.txt new revision: 1.86; previous revision: 1.85 /cvsroot/CMake/CMake/Tests/CTestUpdateBZR.cmake.in,v <-- Tests/CTestUpdateBZR.cmake.in initial revision: 1.1 |
(0016473) Tom Vercauteren (reporter) 2009-05-15 09:41 |
Actually, the comment on bzr 1.14.1 linux 64 bits crashes was just plain wrong. There were indeed some problems when the locale was set to C but they were due to the xml parser of ctest not knowing the encoding used by the bzr xml output plugin. The attached patch (cmake-bzr-clocale-2009-05-15.patch) fixes the issue with the C locale and adds a unit test that was previously failing when the nightly time revision guessing was also used. |
(0016483) Brad King (manager) 2009-05-15 14:58 |
I can't quickly reproduce the locale problem, but there may be a better fix. CTest is using a built-in expat 1.95.2. Perhaps expat 2.0.1 can recognize the "ANSI_X3.4-1968" locale. Try building CMake with the 'CMAKE_USE_SYSTEM_EXPAT' option on, and point it at a system expat 2.0.1. Then see if it can handle the locale without your patch. If so, I'll be happy to update CMake's built-in expat version. Thanks. |
(0016510) Tom Vercauteren (reporter) 2009-05-18 07:44 |
Just tried expat 2.0.1. The "ANSI_X3.4-1968" unknown encoding problem is still there. Also a patch is required for cmake to compile with expat 2.0.1. On line 231 of cmXMLParser.cxx: XML_Parser* parser = static_cast<XML_Parser*>(this->Parser); should be replaced by XML_Parser parser = static_cast<XML_Parser>(this->Parser); Except from that all unit tests pass on my machine with expat 2.0.1. |
(0016519) Brad King (manager) 2009-05-18 10:35 |
Thanks. In that case it is not worth updating the expat version right now. I've committed cmake-bzr-clocale-2009-05-15.patch: BUG: Parse more bzr xml output encodings /cvsroot/CMake/CMake/Source/CTest/cmCTestBZR.cxx,v <-- Source/CTest/cmCTestBZR.cxx new revision: 1.2; previous revision: 1.1 /cvsroot/CMake/CMake/Tests/CMakeLists.txt,v <-- Tests/CMakeLists.txt new revision: 1.87; previous revision: 1.86 |
(0018927) Brad King (manager) 2009-12-16 14:28 |
CTest 2.8.0 supports BZR. See issue 0007541 for updates on the nightly start time issue (which affects all DVCS tools). |
Notes |
Issue History | |||
Date Modified | Username | Field | Change |
2008-04-21 09:00 | TomPouce25 | New Issue | |
2008-07-22 08:45 | Tom Vercauteren | Note Added: 0012797 | |
2008-08-19 14:16 | Bill Hoffman | Status | new => assigned |
2008-08-19 14:16 | Bill Hoffman | Assigned To | => Bill Hoffman |
2008-08-19 14:17 | Bill Hoffman | Assigned To | Bill Hoffman => Brad King |
2008-08-19 14:36 | Brad King | Relationship added | related to 0006994 |
2008-08-25 07:23 | Nicolas Savoire | File Added: patch_cmake_bzr_support.diff | |
2008-08-25 07:40 | Nicolas Savoire | Note Added: 0013168 | |
2008-08-25 09:48 | Brad King | Note Added: 0013171 | |
2008-08-25 15:37 | Brad King | Relationship added | related to 0007541 |
2009-04-22 11:17 | Tom Vercauteren | File Added: cmake-bzr-2009-04-15.patch | |
2009-04-22 11:19 | Tom Vercauteren | Note Added: 0016136 | |
2009-04-22 11:40 | Tom Vercauteren | File Added: cmake-bzr-2009-04-22.patch | |
2009-04-22 11:53 | Brad King | Note Added: 0016137 | |
2009-04-22 11:58 | Tom Vercauteren | Note Added: 0016138 | |
2009-04-22 12:01 | Tom Vercauteren | Note Added: 0016139 | |
2009-04-22 12:46 | Tom Vercauteren | File Added: cmake-bzr-2009-04-22-bis.patch | |
2009-04-22 12:48 | Tom Vercauteren | Note Added: 0016141 | |
2009-04-22 13:20 | Brad King | Note Added: 0016143 | |
2009-04-23 06:22 | Tom Vercauteren | File Added: cmake-bzr-2009-04-23.patch | |
2009-04-23 06:27 | Tom Vercauteren | Note Added: 0016151 | |
2009-04-23 09:03 | Brad King | Note Added: 0016153 | |
2009-04-23 10:25 | Tom Vercauteren | Note Added: 0016157 | |
2009-04-23 10:54 | Brad King | Note Added: 0016159 | |
2009-04-23 10:56 | Brad King | Note Added: 0016160 | |
2009-04-23 10:59 | Tom Vercauteren | Note Added: 0016161 | |
2009-04-23 11:01 | Brad King | Note Added: 0016162 | |
2009-04-23 11:10 | Tom Vercauteren | Note Added: 0016163 | |
2009-04-23 12:03 | Brad King | Note Added: 0016165 | |
2009-04-23 12:03 | Brad King | Note Added: 0016166 | |
2009-04-23 12:03 | Brad King | Note Deleted: 0016165 | |
2009-04-23 13:05 | Tom Vercauteren | Note Added: 0016167 | |
2009-04-23 13:35 | Brad King | Note Added: 0016168 | |
2009-04-23 13:46 | Tom Vercauteren | Note Added: 0016169 | |
2009-05-14 10:14 | Brad King | Note Added: 0016433 | |
2009-05-14 13:37 | Tom Vercauteren | File Added: cmake-bzr-no-nightly-2009-05-14.patch | |
2009-05-14 13:37 | Tom Vercauteren | File Added: cmake-bzr-with-nightly-2009-05-14.patch | |
2009-05-14 13:49 | Tom Vercauteren | Note Added: 0016441 | |
2009-05-14 16:00 | Brad King | Note Added: 0016450 | |
2009-05-14 16:14 | Brad King | Note Added: 0016452 | |
2009-05-15 09:41 | Tom Vercauteren | Note Added: 0016473 | |
2009-05-15 09:42 | Tom Vercauteren | File Added: cmake-bzr-clocale-2009-05-15.patch | |
2009-05-15 14:58 | Brad King | Note Added: 0016483 | |
2009-05-18 07:44 | Tom Vercauteren | Note Added: 0016510 | |
2009-05-18 10:35 | Brad King | Note Added: 0016519 | |
2009-07-10 11:21 | Brad King | Relationship replaced | child of 0007541 |
2009-12-16 14:28 | Brad King | Note Added: 0018927 | |
2009-12-16 14:28 | Brad King | Status | assigned => closed |
2009-12-16 14:28 | Brad King | Resolution | open => fixed |
Issue History |
Copyright © 2000 - 2018 MantisBT Team |