# # add_file "stringtok.hh" # # patch "commands.cc" # from [2923bafee370f9dbbd64184e64f8a36819880c6f] # to [ab99b00be8f0a3f8dc6537c813b45aa35f49c1a7] # # patch "cvs_client.cc" # from [ea6ebe414c3c10dab2d52cd896c1b5bf406fb0e9] # to [8913b3b4b228d8f5282ed03611772e8ea001bd7d] # # patch "cvs_sync.cc" # from [7744452d62d5ecc58a9c60f2158372f55529cfb5] # to [4251b3e625109f6b02a7d705c4994548fc9f77f9] # # patch "cvs_sync.hh" # from [68e39988bc37a431482eaa0209dd27b33764bc2e] # to [d52b51edac779031c529340a22187a4ca4ab43a7] # # patch "monotone.texi" # from [51199c5b99d91e369da807f3f6ea02367036c4b3] # to [6b32463d9c64c91531e8d65699901d2f86eefa16] # # patch "stringtok.hh" # from [] # to [b6d87e59c8230d61ef9deea6a270b6de64f74b05] # ======================================================================== --- commands.cc 2923bafee370f9dbbd64184e64f8a36819880c6f +++ commands.cc ab99b00be8f0a3f8dc6537c813b45aa35f49c1a7 @@ -3666,37 +3666,39 @@ // missing: compression level (-z), cvs-branch (-r), since (-D) -CMD(cvs_pull, "network", "[CVS-REPOSITORY CVS-MODULE]", +CMD(cvs_pull, "network", "[CVS-REPOSITORY CVS-MODULE [CVS-BRANCH]]", "(re-)import a module from a remote cvs repository", OPT_BRANCH_NAME % OPT_SINCE) { + if (args.size() == 1 || args.size() > 3) throw usage(name); - if (args.size() != 2 && args.size() != 0) throw usage(name); - - string repository,module; - if (args.size() == 2) + string repository,module,branch; + if (args.size() >= 2) { repository = idx(args, 0)(); module = idx(args, 1)(); + if (args.size()==3) + branch=idx(args, 2)(); } N(!app.branch_name().empty(), F("no destination branch specified\n")); - cvs_sync::pull(repository,module,app); + cvs_sync::pull(repository,module,branch,app); } -CMD(cvs_push, "network", "[CVS-REPOSITORY CVS-MODULE]", +CMD(cvs_push, "network", "[CVS-REPOSITORY CVS-MODULE [CVS-BRANCH]]", "commit changes in local database to a remote cvs repository", OPT_BRANCH_NAME % OPT_REVISION) { + if (args.size() == 1 || args.size() > 3) throw usage(name); - if (args.size() != 2 && args.size() != 0) throw usage(name); - - string repository,module; - if (args.size() == 2) + string repository,module,branch; + if (args.size() >= 2) { repository = idx(args, 0)(); module = idx(args, 1)(); + if (args.size()==3) + branch=idx(args, 2)(); } - cvs_sync::push(repository,module,app); + cvs_sync::push(repository,module,branch,app); } ======================================================================== --- cvs_client.cc ea6ebe414c3c10dab2d52cd896c1b5bf406fb0e9 +++ cvs_client.cc 8913b3b4b228d8f5282ed03611772e8ea001bd7d @@ -13,6 +13,7 @@ #include "cvs_client.hh" #include #include +#include "stringtok.hh" void cvs_client::writestr(const std::string &s, bool flush) { if (s.size()) L(F("writestr(%s") % s); // s mostly contains the \n char @@ -110,43 +111,7 @@ if (inputbuffer.empty()) goto try_again; } -// an adaptor giving push_back on insert providing containers (sets) -template - class push_back2insert -{ Container &c; -public: - push_back2insert(Container &_c) : c(_c) {} - template - void push_back(const T &t) const { c.insert(t); } -}; - -// inspired by code from Marcelo E. Magallon and the libstdc++ doku -template -void -stringtok (Container &container, std::string const &in, - const char * const delimiters = " \t\n") -{ - const std::string::size_type len = in.length(); - std::string::size_type i = 0; - - while ( i < len ) - { - // find the end of the token - std::string::size_type j = in.find_first_of (delimiters, i); - - // push token - if (j == std::string::npos) { - container.push_back (in.substr(i)); - return; - } else - container.push_back (in.substr(i, j-i)); - - // set up for next loop - i = j + 1; - } -} - -std::string trim(const std::string &s) +static std::string trim(const std::string &s) { std::string::size_type start=s.find_first_not_of(" "); if (start==std::string::npos) return std::string(); std::string::size_type end=s.find_last_not_of(" "); ======================================================================== --- cvs_sync.cc 7744452d62d5ecc58a9c60f2158372f55529cfb5 +++ cvs_sync.cc 4251b3e625109f6b02a7d705c4994548fc9f77f9 @@ -13,13 +13,12 @@ #include #include #include +#include "stringtok.hh" #ifdef WIN32 #define sleep(x) _sleep(x) #endif -#define BACKWARD_COMPATIBLE - using namespace std; // since the piece methods in rcs_import depend on rcs_file I cannot reuse them @@ -60,12 +59,6 @@ using namespace cvs_sync; -#if 0 -std::ostream &operator<<(std::ostream &o, const file_state &f) -{ return o << f.since_when << ' ' << f.cvs_version << ' ' << f.dead; -} -#endif - bool file_state::operator<(const file_state &b) const { return since_when const& c, + std::string &repository, std::string& module, std::string& branch) +{ + cert_value value; + decode_base64(c.inner().value, value); + parse_cvs_cert_header(value, repository, module, branch); +} + +std::string cvs_repository::create_cvs_cert_header() const +{ + // I assume that at least TAB is uncommon in path names - even on Windows + std::string result=host+":"+root+"\t"+module; + if (!branch.empty()) result+="\t"+branch; + return result+"\n"; +} + std::string cvs_repository::debug() const { std::string result; @@ -922,13 +932,8 @@ { std::string file_contents; I(!i->second.known_states.empty()); { std::set::iterator s2=i->second.known_states.begin(); -#if 0 - cvs_client::checkout c=CheckOut2(i->first,s2->cvs_version); - store_checkout(s2,c,file_contents); -#else cvs_client::update c=Update(i->first,s2->cvs_version); store_checkout(s2,c,file_contents); -#endif } for (std::set::iterator s=i->second.known_states.begin(); s!=i->second.known_states.end();++s) @@ -958,8 +963,8 @@ } void cvs_repository::cert_cvs(const cvs_edge &e, packet_consumer & pc) -{ // I assume that at least TAB is uncommon in path names - even on Windows - std::string content=host+":"+root+"\t"+module+"\n"; +{ + std::string content=create_cvs_cert_header(); if (!e.delta_base.inner()().empty()) { content+="+"+e.delta_base.inner()()+"\n"; } @@ -1192,7 +1197,7 @@ { std::vector< revision > certs; app.db.get_revision_certs(*i,certs); std::vector< revision >::const_iterator j=certs.begin(); - std::string to_match=host+":"+root+"\t"+module+"\n"; + std::string to_match=create_cvs_cert_header(); for (;j!=certs.end();++j) { if (j->inner().name()!=cvs_cert_name) continue; cert_value value; @@ -1287,8 +1292,12 @@ store_modules(); } -// this is somewhat clumsy ... rethink it +// look for _any_ cvs cert in the given monotone branch and assign +// its value to repository, module, branch + +// this is somewhat clumsy ... but works well enough static void guess_repository(std::string &repository, std::string &module, + std::string & branch, std::vector< revision > &certs, app_state &app) { I(!app.branch_name().empty()); app.db.get_revision_certs(cvs_cert_name, certs); @@ -1304,22 +1313,13 @@ bi!=branch_certs.end();++bi) { // actually this finds an arbitrary element of the set intersection if (ci->inner().ident==bi->inner().ident) - { cert_value value; - decode_base64(ci->inner().value, value); - std::string::size_type nlpos=value().find('\n'); - I(nlpos!=std::string::npos); - std::string repo=value().substr(0,nlpos); - std::string::size_type lastslash=repo.find('\t'); -#ifdef BACKWARD_COMPATIBLE - if (lastslash==std::string::npos) lastslash=repo.rfind('/'); -#endif - I(lastslash!=std::string::npos); - // this is naive ... but should work most of the time - // we should not separate repo and module by '/' - // but I do not know a much better separator - repository=repo.substr(0,lastslash); - module=repo.substr(lastslash+1); - L(F("using module '%s' in repository '%s'\n") % module % repository); + { + cvs_repository::parse_cvs_cert_header(*ci,repository,module,branch); + if (branch.empty()) + L(F("using module '%s' in repository '%s'\n") % module % repository); + else + L(F("using branch '%s' of module '%s' in repository '%s'\n") + % branch % module % repository); goto break_outer; } } @@ -1328,14 +1328,14 @@ } void cvs_sync::push(const std::string &_repository, const std::string &_module, - app_state &app) + std::string const& _branch, app_state &app) { test_key_availability(app); // make the variables changeable - std::string repository=_repository, module=_module; + std::string repository=_repository, module=_module, branch=_branch; std::vector< revision > certs; if (repository.empty() || module.empty()) - guess_repository(repository, module, certs, app); - cvs_sync::cvs_repository repo(app,repository,module); + guess_repository(repository, module, branch, certs, app); + cvs_sync::cvs_repository repo(app,repository,module,branch); // turned off for DEBUGGING if (!getenv("CVS_CLIENT_LOG")) repo.GzipStream(3); @@ -1354,17 +1354,17 @@ } void cvs_sync::pull(const std::string &_repository, const std::string &_module, - app_state &app) + std::string const& _branch, app_state &app) { test_key_availability(app); // make the variables changeable - std::string repository=_repository, module=_module; + std::string repository=_repository, module=_module, branch=_branch; std::vector< revision > certs; if (repository.empty() || module.empty()) - guess_repository(repository, module, certs, app); - cvs_sync::cvs_repository repo(app,repository,module); -// turned off for DEBUGGING + guess_repository(repository, module, branch, certs, app); + cvs_sync::cvs_repository repo(app,repository,module,branch); +// turn compression on when not DEBUGGING if (!getenv("CVS_CLIENT_LOG")) repo.GzipStream(3); transaction_guard guard(app.db); @@ -1398,20 +1398,13 @@ std::auto_ptr cert_ticker; cert_ticker.reset(new ticker("cvs certs", "C", 10)); - std::string needed_cert=host+":"+root+"\t"+module+"\n"; -#ifdef BACKWARD_COMPATIBLE - std::string needed_cert_old=host+":"+root+"/"+module+"\n"; -#endif + std::string needed_cert=create_cvs_cert_header(); for (vector >::const_iterator i=certs.begin(); i!=certs.end(); ++i) { // populate data structure using these certs cert_value cvs_revisions; decode_base64(i->inner().value, cvs_revisions); if (cvs_revisions().size()>needed_cert.size() - && (cvs_revisions().substr(0,needed_cert.size())==needed_cert -#ifdef BACKWARD_COMPATIBLE - || cvs_revisions().substr(0,needed_cert_old.size())==needed_cert_old -#endif - )) + && (cvs_revisions().substr(0,needed_cert.size())==needed_cert)) { // parse and add the cert ++(*cert_ticker); cvs_edge e(i->inner().ident,app); @@ -1659,28 +1652,22 @@ // we default to the first repository found (which might not be what you wanted) if (command=="manifest" && arg.size()==constants::idlen) { revision_id rid(arg); - // easy but not very efficient way, better would be to retrieve revisions - // recursively (perhaps?) + // easy but not very efficient way, since we parse all revisions to decode + // the delta encoding + // (perhaps?) it would be better to retrieve needed revisions recursively std::vector< revision > certs; app.db.get_revision_certs(rid,cvs_cert_name,certs); + // erase_bogus_certs ? N(!certs.empty(),F("revision has no 'cvs-revisions' certificates\n")); - cert_value cvs_revisions; - decode_base64(certs.front().inner().value, cvs_revisions); - std::string::size_type nl=cvs_revisions().find('\n'); - I(nl!=std::string::npos); - std::string line=cvs_revisions().substr(0,nl); - std::string::size_type slash=line.rfind('/'); - I(slash!=std::string::npos); - cvs_sync::cvs_repository repo(app,line.substr(0,slash),line.substr(slash+1),false); + std::string repository,module,branch; + cvs_repository::parse_cvs_cert_header(certs.front(), repository, module, branch); + cvs_sync::cvs_repository repo(app,repository,module,branch,false); app.db.get_revision_certs(cvs_cert_name, certs); - // erase_bogus_certs ? repo.process_certs(certs); - std::cout << line << '\n'; std::cout << debug_manifest(repo.get_files(rid)); return; } - } const cvs_manifest &cvs_repository::get_files(const revision_id &rid) @@ -1700,37 +1687,6 @@ } } -// inspired by code from Marcelo E. Magallon and the libstdc++ doku -template -void -stringtok (Container &container, std::string const &in, - const char * const delimiters = " \t\n") -{ - const std::string::size_type len = in.length(); - std::string::size_type i = 0; - - while ( i < len ) - { - // eat leading whitespace - // i = in.find_first_not_of (delimiters, i); - // if (i == std::string::npos) - // return; // nothing left but white space - - // find the end of the token - std::string::size_type j = in.find_first_of (delimiters, i); - - // push token - if (j == std::string::npos) { - container.push_back (in.substr(i)); - return; - } else - container.push_back (in.substr(i, j-i)); - - // set up for next loop - i = j + 1; - } -} - void cvs_client::validate_path(const std::string &local, const std::string &server) { for (std::map::const_iterator i=server_dir.begin(); i!=server_dir.end();++i) @@ -1743,7 +1699,8 @@ } void cvs_repository::takeover_dir(const std::string &path) -{ { std::string repository; +{ // remember the server path for this subdirectory + { std::string repository; std::ifstream cvs_repository((path+"CVS/Repository").c_str()); N(cvs_repository.good(), F("can't open %sCVS/Repository\n") % path); std::getline(cvs_repository,repository); @@ -1859,7 +1816,7 @@ // read in directory put into db void cvs_sync::takeover(app_state &app, const std::string &_module) -{ std::string root,module=_module; +{ std::string root,module=_module,branch; N(access("MT",F_OK),F("Found a MT file or directory, already under monotone's control?")); { fstream cvs_root("CVS/Root"); @@ -1867,6 +1824,14 @@ F("can't open ./CVS/Root, please change into the working directory\n")); std::getline(cvs_root,root); } + { fstream cvs_branch("CVS/Tag"); + if (cvs_branch.good()) + { std::getline(cvs_branch,branch); + I(!branch.empty()); + I(branch[0]=='T'); + branch.erase(0,1); + } + } if (module.empty()) { fstream cvs_repository("CVS/Repository"); N(cvs_repository.good(), @@ -1875,15 +1840,15 @@ W(F("Guessing '%s' as the module name\n") % module); } test_key_availability(app); - cvs_sync::cvs_repository repo(app,root,module,false); - // FIXME: validate directory to match the structure + cvs_sync::cvs_repository repo(app,root,module,branch,false); + // FIXME? check that directory layout matches the module structure repo.takeover(); } void cvs_repository::store_modules() { const std::map &sd=GetServerDir(); std::string value; - std::string name=host+":"+root+"\t"+module+"\n"; + std::string name=create_cvs_cert_header(); for (std::map::const_iterator i=sd.begin(); i!=sd.end();++i) { value+=i->first+"\t"+i->second+"\n"; @@ -1898,7 +1863,7 @@ void cvs_repository::retrieve_modules() { if (!GetServerDir().empty()) return; - std::string name=host+":"+root+"\t"+module+"\n"; + std::string name=create_cvs_cert_header(); std::pair key(var_domain("cvs-server-path"), var_name(name)); var_value value; try { ======================================================================== --- cvs_sync.hh 68e39988bc37a431482eaa0209dd27b33764bc2e +++ cvs_sync.hh d52b51edac779031c529340a22187a4ca4ab43a7 @@ -122,6 +122,14 @@ time_t sync_since; +public: + std::string create_cvs_cert_header() const; + static void parse_cvs_cert_header(revision const& c, + std::string &repository, std::string& module, std::string& branch); + static std::string::size_type parse_cvs_cert_header(cert_value const& value, + std::string &repository, std::string& module, std::string& branch); + +private: void check_split(const cvs_file_state &s, const cvs_file_state &end, const std::set::iterator &e); void get_all_files(); @@ -187,9 +195,9 @@ }; void pull(const std::string &repository, const std::string &module, - app_state &app); + std::string const& branch, app_state &app); void push(const std::string &repository, const std::string &module, - app_state &app); + std::string const& branch, app_state &app); void admin(const std::string &command, const std::string &arg, app_state &app); void takeover(app_state &app, const std::string &module); } // end namespace cvs_sync ======================================================================== --- monotone.texi 51199c5b99d91e369da807f3f6ea02367036c4b3 +++ monotone.texi 6b32463d9c64c91531e8d65699901d2f86eefa16 @@ -3138,10 +3138,8 @@ @itemize @item -missing side branch support (@code{--cvsbranch}) +creating new subdirectories on the CVS server is yet unimplemented @item -creating new subdirectories on the CVS server is unimplemented address@hidden a patch (MD5) failure is not handled gracefully @item full history import stresses the server (CVS' fault) @@ -3161,7 +3159,8 @@ direction using the @code{--revision} option. Simply choose the branch you want to see in details in CVS and specify its revision. @item -Working with CVS branches. @code{--cvsbranch}? (not yet implemented) +Working with CVS branches and tags. To be written once I gathered +my own experience with this feature. @end itemize @subsection Future directions: @@ -3915,8 +3914,8 @@ in case someone, for some odd reason, decides to put a ``*'' into their branch name. address@hidden monotone cvs_pull [--since @var{time}] [--cvsbranch @var{branch}] address@hidden @var{module}] address@hidden monotone cvs_push [--since @var{time}] [--cvsbranch @var{branch}] address@hidden @var{module}] address@hidden monotone cvs_pull [--since @var{time}] address@hidden @var{module} address@hidden address@hidden monotone cvs_push address@hidden @var{module} address@hidden These commands communicate with a CVS server. @code{cvs_pull} works like @code{cvs update} and @code{cvs_push} is like @code{cvs commit}. See also @xref{cvssync,,cvssync introduction}. @@ -7116,10 +7115,10 @@ debugging; use cvs_import. @comment TROFF INPUT: .TP address@hidden @b{cvs_pull} @i{[--since=time]} @i{[--cvsbranch=branch]} @i{[repository module]} -Import revisions from a CVS server address@hidden @b{cvs_pull} @i{[--since=time]} @i{[repository module [cvsbranch]]} +Import revisions from a CVS server (check out or update) address@hidden @b{cvs_push} @i{[--cvsbranch=branch]} @i{[repository module]} address@hidden @b{cvs_push} @i{[repository module [cvsbranch]]} Commit revisions to a CVS server @item @b{cvs_takeover} @i{[module]} ======================================================================== --- stringtok.hh +++ stringtok.hh b6d87e59c8230d61ef9deea6a270b6de64f74b05 @@ -0,0 +1,44 @@ +// copyright (C) 2005 Christof Petig +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +// inspired by code from Marcelo E. Magallon and the libstdc++ doku + +#include + +template +void +stringtok (Container &container, std::string const &in, + const char * const delimiters = " \t\n") +{ + const std::string::size_type len = in.length(); + std::string::size_type i = 0; + + while ( i < len ) + { + // find the end of the token + std::string::size_type j = in.find_first_of (delimiters, i); + + // push token + if (j == std::string::npos) { + container.push_back (in.substr(i)); + return; + } else + container.push_back (in.substr(i, j-i)); + + // set up for next loop + i = j + 1; + } +} + +// an adaptor giving push_back on insert providing containers (sets) +template + class push_back2insert +{ Container &c; +public: + push_back2insert(Container &_c) : c(_c) {} + template + void push_back(const T &t) const { c.insert(t); } +}; +