# # add_dir "" # # add_dir "contrib" # # add_dir "contrib/crescendo" # # add_file "contrib/crescendo/README" # content [d49bc4dd7daa67066bb9d3f5f035b787ca00b672] # # add_file "contrib/crescendo/adaptor.hh" # content [a382ba4a06f11fee39b60248b31b65ef5ce24baa] # # add_file "contrib/crescendo/build.sh" # content [634a9c95c9b02b7d84d666e17e8b4e1c77064ed1] # # add_file "contrib/crescendo/constants.cpp" # content [d498aeb1142cf7d32431badd5356df3bf358d36f] # # add_file "contrib/crescendo/crescendo-sanity.cpp" # content [23a98691a324564cdd95a9182001dda00dc61618] # # add_file "contrib/crescendo/crescendo-sanity.hh" # content [f11c6add0d6a38ccd10192fb7c9f84af2109dc39] # # add_file "contrib/crescendo/crescendo.cpp" # content [9500dfe0182b780e8660cee3efc91b724eeb2ba7] # # add_file "contrib/crescendo/crescendo.hh" # content [fbd7d69b7599b386a5b6884b47517de659d22433] # # add_file "contrib/crescendo/monotone.cpp" # content [ad7f5f123151f554203cc8458add408225a59a47] # # add_file "contrib/crescendo/monotone.hh" # content [f84e0e57fe5efe26422db8414458a56138658fb8] # # add_file "contrib/crescendo/monotone_impl.hh" # content [53f8f96fe58aba98a16674b2c27b25bfb1e6092b] --- contrib/crescendo/README +++ contrib/crescendo/README @@ -0,0 +1,32 @@ +This is a client side library for monotone automate + +The license is BSD: + +Copyright (c) 2007, Joel Crisp [Software Risk Solutions Ltd.] +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of "Software Risk Solutions Ltd." nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +It supports: +*) Most (many) monotone automate commands - including important rev graph ones +*) A command queue and callback system for asynchronous +*) Synchronous calls via helper classes +*) Only depends on boost and monotone core + +It is probably very buggy. Known bugs include: +*) Only compiles under cygwin +*) Probably doesn't correctly parse anything other than 7bit ascii +*) Many more complex commands not yet supported +*) Liable to change rapidly + +See crescendo.cpp for usage examples and build.sh for a basic gcc command line. + + +Joel --- contrib/crescendo/adaptor.hh +++ contrib/crescendo/adaptor.hh @@ -0,0 +1,187 @@ +#ifndef H_ADAPTOR_H +#define H_ADAPTOR_H + + +#include +#include +#include "vocab.hh" +#include +#include +#include +#include "crescendo.hh" +#include + +using namespace boost; + +/** + * Main crescendo namespace + */ +namespace crescendo { + + /** + * Namespace for monotone specific crescendo classes + */ + namespace monotone { + + /** + * Adaptor base class used to make synchronous calls to monotone. + * use the wait_for_completion() method. You should sub-class + * this class and override the particularly callback you're interested + * in. + */ + class monotone_adaptor: public monotone_listener { + + private: + /** + * Command completion mutex + */ + boost::mutex cmd_mutex; + + /** + * Command completion notification condition + */ + boost::condition cmd_notify; + + /** + * Command completion status flag + */ + bool complete; + + public: + /** + * Construct a new monotone_adaptor + */ + monotone_adaptor() { + complete=false; + } + + /** + * Block current thread until this command has completed. + */ + void wait_for_completion() { + boost::mutex::scoped_lock l(cmd_mutex); + cmd_notify.wait(l); + } + + /** + * Callback implementation to signal this command complete. + * This is called by the monotone worker thread and should + * not be called by the user. + */ + virtual void command_complete() { + boost::mutex::scoped_lock l(cmd_mutex); + cmd_notify.notify_all(); + complete=true; + } + + /** + * Polling method to test if this command is complete. + */ + bool is_complete() { + boost::mutex::scoped_lock l(cmd_mutex); + return complete; + } + }; + + /** + * Monotone callback adaptor for commands which result in a list + * of revision_id. + */ + class revision_id_list_adaptor: public monotone_adaptor { + + private: + /** + * The list which will be populated with the revision_id from the command result + */ + revision_id_list list; + + public: + revision_id_list_adaptor(): list() { } + virtual ~revision_id_list_adaptor() { } + + /** + * Callback implementation for a revision_id stanza. + * This is called by the monotone worker thread and should + * not be called by the user. + */ + virtual void stanza_revision_id(const revision_id &revision) { + list.push_back(revision); + } + + /** + * Get the list of revision_id. This should only be called after + * wait_for_completion() has returned. + * + * @return the list of revision_id from the command or an empty list + */ + const revision_id_list &get_list() const { + return list; + } + }; + + class tag_list_adaptor: public monotone_adaptor { + private: + tag_list list; + + public: + virtual ~tag_list_adaptor() { } + virtual void stanza_tag(const tag &tag) { + list.push_back(tag); + } + const tag_list &get_list() const { return list; } + }; + + class status_list_adaptor: public monotone_adaptor { + private: + status_list list; + + public: + virtual ~status_list_adaptor() { } + virtual void stanza_file_status(const status &status) { + list.push_back(status); + } + const status_list &get_list() const { return list; } + + }; + + class branch_list_adaptor: public monotone_adaptor { + private: + branch_list list; + public: + branch_list_adaptor(): list() { }; + virtual ~branch_list_adaptor() { }; + virtual void stanza_branch(const std::string &branch) { + list.push_back(branch); + } + const branch_list &get_list() const { return list; } + }; + + class cert_list_adaptor: public monotone_adaptor { + private: + cert_list list; + + public: + virtual ~cert_list_adaptor() { } + virtual void stanza_cert(const cert &cert) { + list.push_back(cert); + } + const cert_list &get_list() const { return list; } + }; + + class key_info_list_adaptor: public monotone_adaptor { + private: + key_info_list list; + + public: + virtual ~key_info_list_adaptor() { } + virtual void stanza_key_info(const key_info &key_info) { + list.push_back(key_info); + } + const key_info_list &get_list() const { return list; } + }; + + } +} + +#endif + --- contrib/crescendo/build.sh +++ contrib/crescendo/build.sh @@ -0,0 +1,4 @@ +MTN=../../../monotone/net.venge.monotone +gcc -g -fno-strict-aliasing -W -o crescendo -Wall -Wno-unused -I/usr/include/boost-1_33_1 -I${MTN} *.cpp ${MTN}/vocab.cc ${MTN}/sanity.cc ${MTN}/simplestring_xform.cc -lstdc++ -lboost_thread-gcc-mt -lboost_filesystem-gcc-mt -lintl + +#gcc -fprofile-arcs -ftest-coverage -g -fno-strict-aliasing -W -o crescendo -Wall -Wno-unused -I/usr/include/boost-1_33_1 -I../monotone/net.venge.monotone *.cpp ../monotone/net.venge.monotone/vocab.cc ../monotone/net.venge.monotone/sanity.cc ../monotone/net.venge.monotone/simplestring_xform.cc -lstdc++ -lboost_thread-gcc-mt -lboost_filesystem-gcc-mt -lintl--- contrib/crescendo/constants.cpp +++ contrib/crescendo/constants.cpp @@ -0,0 +1,172 @@ +// Copyright (C) 2002 Graydon Hoare +// +// This program is made available under the GNU GPL version 2.0 or +// greater. See the accompanying file COPYING for details. +// +// This program is distributed WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. + +// this file contains magic constants which you could, in theory, tweak. +// probably best not to tweak them though. + +#include "constants.hh" +#include "numeric_vocab.hh" + +#include + +using std::string; + +namespace constants +{ + + // number of bits in an RSA key we use + size_t const keylen = 1024; + + // number of seconds in window, in which to consider CVS commits equivalent + // if they have otherwise compatible contents (author, changelog) + time_t const cvs_window = 60 * 5; + + // size of a line of database traffic logging, beyond which lines will be + // truncated. + size_t const db_log_line_sz = 70; + + size_t const default_terminal_width = 72; + + // size in bytes of the database xdelta version reconstruction cache. + // the value of 7 MB was determined as the optimal point after timing + // various values with a pull of the monotone repository - it could + // be tweaked further. + size_t const db_version_cache_sz = 7 * (1 << 20); + + // the value of 7 MB was determined by blindly copying the line above and + // not doing any testing at all - it could be tweaked further. + size_t const db_roster_cache_sz = 7 * (1 << 20); + + // this value is very much an estimate. the calculation is: + // -- 40 bytes content hash + // -- a path component, maybe 10 or 15 bytes + // -- 40 bytes birth revision + // -- 40 bytes name marking hash + // -- 40 bytes content marking hash + // -- plus internal pointers, etc., for strings, sets, shared_ptrs, heap + // overhead, ... + // -- plus any space taken for attrs + // so ~175 bytes for a file node, plus internal slop, plus attrs (another + // 60 bytes per attr, or so), minus 80 bytes for dir nodes. So this just + // picks a number that seems a reasonable amount over 175. + size_t const db_estimated_roster_node_sz = 210; + + unsigned long const db_max_delayed_file_bytes = 16 * 1024 * 1024; + + // size of a line of text in the log buffer, beyond which log lines will be + // truncated. + size_t const log_line_sz = 0x300; + + // all the ASCII characters (bytes) which are legal in a packet. + char const * const legal_packet_bytes = + // LDH characters + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "-" + // extra base64 codes + "+/=" + // separators + "address@hidden" + // whitespace + " \r\n\t" + ; + + string const regex_legal_packet_bytes("([a-zA-Z0-9+/=[:space:]]+)"); + + // all the ASCII characters (bytes) which are legal in a SHA1 hex id + char const * const legal_id_bytes = + "0123456789abcdef" + ; + + string const regex_legal_id_bytes("([0-9a-f]{40})"); + + // all the ASCII characters (bytes) which are legal in an ACE string + char const * const legal_ace_bytes = + // LDH characters + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "-" + // label separators + ".@" + ; + + // all the ASCII characters (bytes) which can occur in cert names + char const * const legal_cert_name_bytes = + // LDH characters + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "-" + ; + + string const regex_legal_cert_name_bytes("([-a-zA-Z0-9]+)"); + + // all the ASCII characters (bytes) which can occur in key names + char const * const legal_key_name_bytes = + // LDH characters + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "-" + // other non-shell, non-selector metacharacters allowed in (unquoted) local + // parts by RFC2821/RFC2822. The full list is !#$%&'*+-/=?^_`|{}~. + "+_." + // label and component separators + ".@" + ; + + string const regex_legal_key_name_bytes("(address@hidden)"); + + // merkle tree / netcmd / netsync related stuff + + size_t const merkle_fanout_bits = 4; + + // all other merkle constants are derived + size_t const merkle_hash_length_in_bits = merkle_hash_length_in_bytes * 8; + size_t const merkle_num_tree_levels = merkle_hash_length_in_bits / merkle_fanout_bits; + size_t const merkle_num_slots = 1 << merkle_fanout_bits; + size_t const merkle_bitmap_length_in_bits = merkle_num_slots * 2; + size_t const merkle_bitmap_length_in_bytes = merkle_bitmap_length_in_bits / 8; + + BOOST_STATIC_ASSERT(sizeof(char) == 1); + BOOST_STATIC_ASSERT(CHAR_BIT == 8); + BOOST_STATIC_ASSERT(merkle_num_tree_levels > 0); + BOOST_STATIC_ASSERT(merkle_num_tree_levels < 256); + BOOST_STATIC_ASSERT(merkle_fanout_bits > 0); + BOOST_STATIC_ASSERT(merkle_fanout_bits < 32); + BOOST_STATIC_ASSERT(merkle_hash_length_in_bits > 0); + BOOST_STATIC_ASSERT((merkle_hash_length_in_bits % merkle_fanout_bits) == 0); + BOOST_STATIC_ASSERT(merkle_bitmap_length_in_bits > 0); + BOOST_STATIC_ASSERT((merkle_bitmap_length_in_bits % 8) == 0); + + u8 const netcmd_current_protocol_version = 6; + + size_t const netcmd_minimum_bytes_to_bother_with_gzip = 0xfff; + + size_t const netsync_session_key_length_in_bytes = 20; // 160 bits + size_t const netsync_hmac_value_length_in_bytes = 20; // 160 bits + + netsync_session_key const netsync_key_initializer(string(netsync_session_key_length_in_bytes, 0)); + + // attributes + string const encoding_attribute("mtn:encoding"); + string const manual_merge_attribute("mtn:manual_merge"); + string const binary_encoding("binary"); + string const default_encoding("default"); +} + +// Local Variables: +// mode: C++ +// fill-column: 76 +// c-file-style: "gnu" +// indent-tabs-mode: nil +// End: +// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: --- contrib/crescendo/crescendo-sanity.cpp +++ contrib/crescendo/crescendo-sanity.cpp @@ -0,0 +1,61 @@ +#include "crescendo-sanity.hh" + +extern sanity & global_sanity; +crescendo_sanity real_sanity; +sanity & global_sanity = real_sanity; + +crescendo_sanity::crescendo_sanity() : relaxed(false) +{} + +crescendo_sanity::~crescendo_sanity() +{} + +void +crescendo_sanity::initialize(int argc, char ** argv, char const * lc_all) +{ + std::string full_version_string="1.0"; + // get_full_version(full_version_string); + PERM_MM(full_version_string); + + this->sanity::initialize(argc, argv, lc_all); +} + +void +crescendo_sanity::set_relaxed(bool rel) +{ + relaxed = rel; +} + +void +crescendo_sanity::inform_log(std::string const &msg) +{ + // ui.inform(msg); +} + +void +crescendo_sanity::inform_message(std::string const &msg) +{ + // ui.inform(msg); +} + +void +crescendo_sanity::inform_warning(std::string const &msg) +{ + // ui.warn(msg); +} + +void +crescendo_sanity::inform_error(std::string const &msg) +{ + // ui.inform(msg); +} + + +// Local Variables: +// mode: C++ +// fill-column: 76 +// c-file-style: "gnu" +// indent-tabs-mode: nil +// End: +// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: + --- contrib/crescendo/crescendo-sanity.hh +++ contrib/crescendo/crescendo-sanity.hh @@ -0,0 +1,35 @@ +#ifndef __CRESCENDO_SANITY_HH__ +#define __CRESCENDO_SANITY_HH__ + +#include "sanity.hh" + +struct crescendo_sanity : public sanity +{ + bool relaxed; + + crescendo_sanity(); + ~crescendo_sanity(); + void initialize(int, char **, char const *); + + void set_relaxed(bool rel); + +private: + void inform_log(std::string const &msg); + void inform_message(std::string const &msg); + void inform_warning(std::string const &msg); + void inform_error(std::string const &msg); +}; + +extern crescendo_sanity real_sanity; + +#endif + + +// Local Variables: +// mode: C++ +// fill-column: 76 +// c-file-style: "gnu" +// indent-tabs-mode: nil +// End: +// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: + --- contrib/crescendo/crescendo.cpp +++ contrib/crescendo/crescendo.cpp @@ -0,0 +1,86 @@ +#include "crescendo.hh" +#include "monotone.hh" +#include "adaptor.hh" +#include + +using namespace crescendo; +using namespace crescendo::monotone; + +void get_system_flavour(std::string & ident) +{ + ident.append("Foo!"); +} + +int main() { + // Get the monotone factory + monotone_factory factory; + + // Ask it for an instance of monotone to talk to + shared_ptr mtn=factory.get_monotone("../safe/monotone.db","."); + + // We are going to use an asynchronous call + // So get a useful blist adaptor + branch_list_adaptor *blist=new branch_list_adaptor(); + + // Actually do the command + mtn->branches(blist); + + // Wait for the command to complete + blist->wait_for_completion(); + + // Now print out the results + { + branch_list::const_iterator iterator=blist->get_list().begin(); + branch_list::const_iterator end=blist->get_list().end(); + while(iterator!=end) { + std::cerr << "[" << *iterator << "]\n"; + ++iterator; + } + } + + std::cerr << "Command completed, dumping...\n"; + revision_id_list_adaptor *rlist=new revision_id_list_adaptor(); + mtn->heads(*blist->get_list().begin(),rlist); + rlist->wait_for_completion(); + + std::cerr << "Command completed, dumping...\n"; + std::cerr << "Size is " << rlist->get_list().size() << "\n"; + { + revision_id_list::const_iterator iterator=rlist->get_list().begin(); + revision_id_list::const_iterator end=rlist->get_list().end(); + while(iterator!=end) { + + revision_id id=*iterator; + std::cerr << "[" << id << "]\n"; + + ++iterator; + + } + } + + tag_list_adaptor *tlist=new tag_list_adaptor(); + mtn->tags("*",tlist); + tlist->wait_for_completion(); + delete tlist; + + cert_list_adaptor *clist=new cert_list_adaptor(); + mtn->certs(*rlist->get_list().begin(),clist); + clist->wait_for_completion(); + delete clist; + + key_info_list_adaptor *klist=new key_info_list_adaptor(); + mtn->keys(klist); + klist->wait_for_completion(); + delete klist; + + std::cerr << "All complete\n"; + + // Cleanup our rlist object + delete rlist; + + // Cleanup our blist object + delete blist; + + mtn->close_monotone(); + +} --- contrib/crescendo/crescendo.hh +++ contrib/crescendo/crescendo.hh @@ -0,0 +1,158 @@ +#ifndef H_CRESCENDO_H +#define H_CRESCENDO_H + +#include +#include +#include +#include +#include "vocab.hh" +#include +#include +#include +#include +#include +#include + +using namespace boost; +namespace fs = boost::filesystem; + + +namespace crescendo { + typedef std::vector< std::string > stringv; + typedef std::vector< revision_id > revision_id_list; + typedef std::vector< std::string > branch_list; + + + class revision_change {}; + typedef std::vector< shared_ptr < revision_change > > revision_change_list; + + class revision { + private: + revision_id id; + manifest_id manifest; + revision_id first_old_revision; + revision_id second_old_revision; + revision_change_list change; + revision(const revision_id &rev_id,const manifest_id &rev_manifest, const revision_id &rev_first_old_revision, const revision_id &rev_second_old_revision, const revision_change_list &rev_change): id(rev_id), manifest(rev_manifest), first_old_revision(rev_first_old_revision), second_old_revision(rev_second_old_revision), change(rev_change) {}; + + public: + revision() {}; + const revision_id &getId() const { return id; } + const manifest_id &getManifest() const { return manifest; } + const revision_id &getFirstOldRevision() const { return first_old_revision; } + const revision_id &getSecondOldRevision() const { return second_old_revision; } + const revision_change_list &getChanges() const { return change; } + static shared_ptr< const revision > parse(std::istream source) { + // TODO: implement + return shared_ptr< const revision >(new revision()); + } + }; + + class manifest_file { + private: + fs::path file; + file_id id; + }; + + class manifest_dir { + private: + fs::path dir; + file_id id; + }; + + typedef std::vector< manifest_file > manifest_file_list; + typedef std::vector< manifest_dir > manifest_dir_list; + + class manifest { + private: + manifest_id id; + manifest_file_list files; + manifest_dir_list dirs; + }; + + class tag { + private: + std::string tag_name; + revision_id id; + std::string signer; + branch_list branches; + public: + tag(std::string &r_tag_name,revision_id &r_id,std::string &r_signer,branch_list &r_branches): tag_name(r_tag_name),id(r_id),signer(r_signer),branches(r_branches) { + } + }; + + typedef std::vector< tag > tag_list; + + enum pre_state { pre_unchanged, pre_deleted, pre_renamed }; + enum post_state { post_unchanged, post_renamed, post_added }; + enum file_state { unknown, patched, unknown_unincluded, ignored_unincluded, missing }; + enum rename { left, right }; + + class status { + private: + fs::path path; + enum pre_state pre_state; + enum post_state post_state; + enum file_state file_state; + enum rename rename[2]; + }; + + typedef std::vector < status > status_list; + + class cert { + private: + std::string key; + std::string signature; + std::string name; + std::string value; + std::string trust; + public: + cert(std::string &r_key,std::string &r_signature,std::string &r_name,std::string &r_value,std::string &r_trust): key(r_key),signature(r_signature),name(r_name),value(r_value),trust(r_trust) { + }; + + }; + + typedef std::vector< cert > cert_list; + + typedef std::string selector; + + class attribute { + private: + std::string name; + std::string value; + }; + + enum attribute_state { added, dropped, unchanged, changed }; + + class file_attribute { + private: + attribute attribute_value; + enum attribute_state state; + }; + + typedef std::vector< file_attribute > file_attribute_list; + + class key_info { + private: + std::string name; + std::string public_hash; + std::string private_hash; + stringv public_location; + stringv private_location; + public: + key_info(std::string r_name,std::string &r_public_hash,std::string &r_private_hash,stringv &r_public_location,stringv &r_private_location): name(r_name),public_hash(r_public_hash),private_hash(r_private_hash),public_location(r_public_location),private_location(r_private_location) { } + + }; + + class content_difference { + }; + + typedef std::vector< key_info > key_info_list; + + typedef adjacency_list< setS /* OutEdgeList */, vecS /* VertexList */, directedS /* Directed */, shared_ptr< const revision >/* VertexProperties */, no_property /* EdgeProperties */> revision_graph; + + typedef std::vector< fs::path > file_list; + +} + +#endif --- contrib/crescendo/monotone.cpp +++ contrib/crescendo/monotone.cpp @@ -0,0 +1,871 @@ +#include "monotone.hh" +#include "monotone_impl.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//using fs = boost::filesystem; + +namespace crescendo { + namespace monotone { + class spawn_exception: public std::exception { }; + class bad_read_exception: public std::exception { }; + class bad_write_exception: public std::exception { }; + class bad_version_exception: public std::exception { }; + class bad_format_exception: public std::exception { }; + class out_of_order_exception: public std::exception { }; + + /** + * Open a connection to monotone and return an instance of the interface to it + * + * @param db the path to the monotone database + * @param working the path to the working directory + */ + const shared_ptr< monotone > monotone_factory::get_monotone(fs::path db,fs::path working) { + pid_t pid; + fdx_t stdin_pipe[2]; + fdx_t stdout_pipe[2]; + // fd_t stderr_pipe[2]; + + pipe(stdin_pipe); + pipe(stdout_pipe); + // pipe(stderr_pipe); share err for now + pid=fork(); + if(pid==-1) throw spawn_exception(); + if(pid==0) { + // Child + + // close STDIN + close(0); + + // Connect to parent STDIN pipe + dup(stdin_pipe[0]); + // Close both pipe end in the array + close(stdin_pipe[0]); + close(stdin_pipe[1]); + + // Same again for STDOUT + close(1); + dup(stdout_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + + std::string arg("--db="); + arg.append(db.native_file_string()); + std::cerr << arg << "\n"; + chdir(working.native_file_string().c_str()); + if(execlp(MONOTONE_EXE.c_str(),MONOTONE_EXE.c_str(),arg.c_str(),"automate","stdio",NULL)==-1) { + // Gack - we're in a sub-process. Probably should signal the parent + fprintf(stderr,"FAILED"); + } + return shared_ptr< monotone >(); + } + else { + // Close pipe ends which are now in the child + close(stdin_pipe[0]); + close(stdout_pipe[1]); + return shared_ptr< monotone >(new monotone_impl(stdin_pipe[1],stdout_pipe[0])); + } + } + + /** + * Format a command name and arguments into the input packet expected + * by monotone + * + * @param cmd the string command name + * @param args a vector of arguments (not null, but may be empty) + */ + const std::string monotone_impl::make_command(const std::string cmd,std::vector< std::string > args) { + // All monotone automataion commands start with 'l' + std::string result("l"); + // Now the command name prefixed by the length and a ':' + result.append(boost::lexical_cast(cmd.size())); + result.append(":"); + result.append(cmd); + // Now each argument similarly prefixed + std::vector< std::string >::iterator iterator=args.begin(); + while(iterator!=args.end()) { + result.append(boost::lexical_cast((*iterator).size())); + result.append(":"); + result.append(*iterator); + ++iterator; + } + // And finally an 'e' to end the packet and a CR-LF to boot it + result.append("e\n"); + return result; + } + + /** + * Decode the header of a packet in the response from monotone + * + * @param header the struct into which the decoded information is placed + */ + const void monotone_impl::decode_packet_header(packet_header &header) { + std::string assemble_buffer; + char buf; + int cread; + int terminator_count=0; + + // Header format is four ascii fields terminated by ':' + while(terminator_count<4) { + cread=read(mtn_stdout,&buf,1); + if(cread!=1) throw new bad_read_exception(); + if(buf==10) throw new bad_read_exception(); + if(buf==':') { + terminator_count++; + switch(terminator_count) { + case 1: + header.command_index=boost::lexical_cast(assemble_buffer); + break; + case 2: + header.error_code=boost::lexical_cast(assemble_buffer); + break; + case 3: + if(assemble_buffer[0]!='m') header.last=true; + break; + case 4: + header.packet_size=boost::lexical_cast(assemble_buffer); + return; + } + assemble_buffer.clear(); + } + else assemble_buffer.push_back(buf); + } + } + + /** + * Send a monotone command immediately and read the result. + * This is an internal method and should not be called from + * outside as it will bypass the command queue. Monotone must + * be idle before calling this method + * + * @param cmd the command to send + * @param args a vector of arguments (not null, but may be empty) + */ + const std::string monotone_impl::send_immediate(const std::string cmd, const std::vector < std::string > args) { + + // Format the command + std::string cmd_packet=make_command(cmd,args); + + // Send it - a single write should be sufficient here + int cwritten=write(mtn_stdin,cmd_packet.c_str(),cmd_packet.size()); + if(cwritten!=(int)cmd_packet.size()) throw new bad_write_exception(); + + std::string response; + read_response(response); + return response; + } + + /** + * Read a complete response from monotone. + * This reads all the packets in a response possibly made up from multiple packets. + * + * @param response the string into which the response is written + */ + const void monotone_impl::read_response(std::string &response) { + // Read a reponse possibly made up from multiple packets + int cmd_index=-1; + packet_header header; + do { + // Read the header of the response packet + decode_packet_header(header); + if(header.error_code!=0) throw new bad_format_exception(); + if(header.packet_size>MONOTONE_MAX_PACKET) throw new bad_format_exception(); + if(cmd_index==-1) cmd_index=header.command_index; + if(cmd_index!=header.command_index) throw new out_of_order_exception(); + + // Read the content of the response + char buffer[header.packet_size+1]; + int high_water=0; + while(high_water worker_fn; + worker_fn=boost::bind(&monotone_impl::do_work,ref(*this)); + worker.create_thread(worker_fn); + + } + + /** + * Submit a command to the monotone command queue. + * This method returns immediately, and callbacks are invoked + * when the command reaches the head of the queue and starts to execute. + * + * + * @param cmd the command to send + * @param args a vector of arguments (not null, but may be empty) + * @param callback the object describing the callback request + */ + void monotone_impl::queue_command(const monotone_commands cmd,const std::vector < std::string > args, MonotoneCallback callback) { + assert(callback); + std::cerr << "QUEUING COMMAND : " << cmd_text[cmd] << "\n"; + work_item work(cmd,make_command(cmd_text[cmd],args),callback); + { + mutex::scoped_lock l(work_queue_mutex); + while(worker_purge_queue) sleep(100); // TODO: Remove this hack and do it properly + work_queue.push_back(work); + work_queue_notify.notify_all(); + } + } + + /** + * Thread to manage communication with monotone + */ + void monotone_impl::do_work() { + std::cerr << "WORKING!\n"; + work_item job; + + // Loop until we are told to exit + while(!worker_should_exit) { + + { + // Lock the queue while we are examining it + boost::mutex::scoped_lock queue_guard(work_queue_mutex); + + // Check for the queue being empty + if(work_queue.empty()) { + // No work to do. Wait for some. This will yield + // the mutex until a call to work_queue_notify.notify + worker_busy=false; + work_queue_notify.wait(queue_guard); + } + + // Check to see if our instruction is to exit + if(worker_should_exit) break; + + // Check to see if we have been instructed to purge the queue + if(worker_purge_queue) { + work_queue.clear(); + worker_purge_queue=false; + continue; + } + + // Got some real work to do - pop the next item from the queue + // and execute it + worker_busy=true; + job=work_queue.front(); + work_queue.erase(work_queue.begin()); + + // Now release the lock as we are done examining the queue + } + + try { + dispatch_job(job); + } + catch(std::exception e) { + // TODO: Better error handling here + std::cerr << "WORKER: Exception\n"; + } + } + std::cerr << "WORKER STOPPED\n"; + } + + /** + * Actually send a command to the monotone instance and handle the + * response + */ + void monotone_impl::dispatch_job(const work_item &work) { + std::cerr << "DISPATCHING JOB " << work.get_raw_command() << "\n"; + + // Tell the callback we have started the command + work.get_callback()->command_started(); + + // Send the command + std::string raw_cmd=work.get_raw_command(); + int cwritten=write(mtn_stdin,raw_cmd.c_str(),raw_cmd.size()); + if(cwritten!=(int)raw_cmd.size()) throw new bad_write_exception(); + + // Read a reponse possibly made up from multiple packets + // For each stanza in the response, invoke the callback + std::string response; + int cmd_index=-1; + packet_header header; + do { + // Read the header of the response packet + decode_packet_header(header); + + if(header.error_code!=0) { + if(header.error_code==1) throw new bad_format_exception(); + // OK, we have an error message in the stream. Drain it to a + // buffer then dispatch to the error handler + char buffer[256]; + int cread=0; + std::string error_msg; + while((cread=read(mtn_stdout,buffer,255))>0) { + buffer[cread]=0; + error_msg.append(buffer); + } + work.get_callback()->command_error(error_msg); + return; + } + + if(header.packet_size>MONOTONE_MAX_PACKET) throw new bad_format_exception(); + if(cmd_index==-1) cmd_index=header.command_index; + if(cmd_index!=header.command_index) throw new out_of_order_exception(); + + // Read the content of the response + char buffer[header.packet_size+1]; + int high_water=0; + while(high_watercommand_complete(); + + std::cerr << "DONE:[" << response << "]\n"; + // Check to see that we sucessfully parsed the entire response + if(!response.empty()) throw new bad_format_exception(); + } + + void monotone_impl::parse(const work_item &work,std::string &response) { + // I loathe switch, but barring a mad class explosion this + // is probably the best? TODO: reconsider this decision + switch(work.get_cmd()) { + case cmd_branches: { + parse_branches(work,response); break; + } + case cmd_erase_ancestors: + case cmd_parents: + case cmd_children: + case cmd_ancestors: + case cmd_common_ancestors: + case cmd_descendents: + case cmd_toposort: + case cmd_ancestry_difference: + case cmd_get_base_revision_id: + case cmd_get_current_revision_id: + case cmd_heads: { + parse_revisions(work,response); break; + } + case cmd_tags: { + parse_tags(work,response); break; + } + case cmd_certs: { + parse_certs(work,response); break; + } + case cmd_keys: { + parse_keys(work,response); break; + } + + default: throw new bad_format_exception(); // temp hack + } + } + + + void monotone_impl::parse_branches(const work_item &work,std::string &response) { + int terminator; + while(!response.empty() && (terminator=response.find(10))!=(int)response.size()) { + // Copy our branch line (always only one line) + std::string raw_branch=response.substr(0,terminator); + // Consume the line + response.erase(0,terminator+1); + // Invoke callback + work.get_callback()->stanza_branch(raw_branch); + } + } + + void monotone_impl::parse_revisions(const work_item &work,std::string &response) { + int terminator; + while(!response.empty() && (terminator=response.find(10))!=(int)response.size()) { + // Copy our id line (always only one line) + std::string raw_id=response.substr(0,terminator); + // Consume the line + response.erase(0,terminator+1); + revision_id id(raw_id); + + // Invoke callback + std::cerr << raw_id << " {" << id << "}\n"; + work.get_callback()->stanza_revision_id(id); + } + } + + void monotone_impl::branches(MonotoneCallback callback) { + assert(callback); + queue_command(cmd_branches, empty_args, callback); + } + + void monotone_impl::heads(const std::string &branch,MonotoneCallback callback) { + assert(callback); + stringv args; + args.push_back(branch); + queue_command(cmd_heads, args, callback); + } + + void monotone_impl::leaves(MonotoneCallback callback) { + assert(callback); + queue_command(cmd_heads, empty_args, callback); + } + + void monotone_impl::get_base_revision_id(MonotoneCallback callback) { + assert(callback); + queue_command(cmd_get_base_revision_id, empty_args, callback); + } + + void monotone_impl::get_current_revision_id(MonotoneCallback callback) { + assert(callback); + queue_command(cmd_get_current_revision_id, empty_args, callback); + } + + void monotone_impl::ancestors(const revision_id_list &id,MonotoneCallback callback) { + assert(callback); + assert(id.size()>0); + stringv args; + revision_id_list::const_iterator iterator=id.begin(); + revision_id_list::const_iterator end=id.end(); + while(iterator!=end) { + args.push_back((*iterator).inner()()); + ++iterator; + } + queue_command(cmd_ancestors, args, callback); + } + + void monotone_impl::common_ancestors(const revision_id_list &id,MonotoneCallback callback) { + assert(callback); + assert(id.size()>0); + stringv args; + revision_id_list::const_iterator iterator=id.begin(); + revision_id_list::const_iterator end=id.end(); + while(iterator!=end) { + args.push_back((*iterator).inner()()); + ++iterator; + } + queue_command(cmd_common_ancestors, args, callback); + } + + void monotone_impl::parents(const revision_id &id,MonotoneCallback callback) { + assert(callback); + stringv args; + args.push_back(id.inner()()); + queue_command(cmd_parents, args, callback); + } + + void monotone_impl::tags(const std::string &pattern,MonotoneCallback callback) { + assert(callback); + stringv args; + args.push_back(pattern); + queue_command(cmd_tags, args, callback); + } + + void monotone_impl::certs(const revision_id &id,MonotoneCallback callback) { + assert(callback); + stringv args; + args.push_back(id.inner()()); + queue_command(cmd_certs, args, callback); + } + + void monotone_impl::keys(MonotoneCallback callback) { + assert(callback); + queue_command(cmd_keys, empty_args, callback); + } + + void monotone_impl::descendents(const revision_id_list &id,MonotoneCallback callback) { + assert(callback); + assert(id.size()>0); + stringv args; + revision_id_list::const_iterator iterator=id.begin(); + revision_id_list::const_iterator end=id.end(); + while(iterator!=end) { + args.push_back((*iterator).inner()()); + ++iterator; + } + queue_command(cmd_descendents, args, callback); + } + + void monotone_impl::children(const revision_id &id,MonotoneCallback callback) { + assert(callback); + stringv args; + args.push_back(id.inner()()); + queue_command(cmd_children, args, callback); + } + + void monotone_impl::erase_ancestors(const revision_id_list &id,MonotoneCallback callback) { + assert(callback); + assert(id.size()>0); + stringv args; + revision_id_list::const_iterator iterator=id.begin(); + revision_id_list::const_iterator end=id.end(); + while(iterator!=end) { + args.push_back((*iterator).inner()()); + ++iterator; + } + queue_command(cmd_erase_ancestors, args, callback); + } + + void monotone_impl::toposort(const revision_id_list &id,MonotoneCallback callback) { + assert(callback); + assert(id.size()>0); + stringv args; + revision_id_list::const_iterator iterator=id.begin(); + revision_id_list::const_iterator end=id.end(); + while(iterator!=end) { + args.push_back((*iterator).inner()()); + ++iterator; + } + queue_command(cmd_toposort, args, callback); + } + + void monotone_impl::ancestry_difference(const revision_id &new_id,const revision_id_list &old_id,MonotoneCallback callback) { + assert(callback); + stringv args; + args.push_back(new_id.inner()()); + revision_id_list::const_iterator iterator=old_id.begin(); + revision_id_list::const_iterator end=old_id.end(); + while(iterator!=end) { + args.push_back((*iterator).inner()()); + ++iterator; + } + queue_command(cmd_ancestry_difference, args, callback); + } + + void sink_whitespace(std::string &data) { + while(data[0]==' ') data.erase(0,1); + } + + bool count_four_lines(std::string &data) { + int line_count=0; + size_t cursor=0; + while(line_count<4 && cursorstanza_tag(the_stanza); + return; + } + // More data needed + } + + std::string parse_certs_key(std::string &response) { + sink_whitespace(response); + if(response.find("key")!=0) throw new bad_format_exception(); + response.erase(0,4); + std::string key=parse_quoted_string(response); + response.erase(0,1); // LF + return key; + } + + std::string parse_certs_sig(std::string &response) { + if(response.find("signature")!=0) throw new bad_format_exception(); + response.erase(0,10); + std::string sig=parse_quoted_string(response); + response.erase(0,1); // LF + return sig; + } + + std::string parse_certs_value(std::string &response) { + sink_whitespace(response); + if(response.find("value")!=0) throw new bad_format_exception(); + response.erase(0,6); + std::string val=parse_quoted_string(response); + response.erase(0,1); // LF + return val; + } + + std::string parse_certs_name(std::string &response) { + sink_whitespace(response); + if(response.find("name")!=0) throw new bad_format_exception(); + response.erase(0,5); + std::string name=parse_quoted_string(response); + response.erase(0,1); // LF + return name; + } + + std::string parse_keys_name(std::string &response) { + sink_whitespace(response); + if(response.find("name")!=0) throw new bad_format_exception(); + response.erase(0,5); + std::string name=parse_quoted_string(response); + response.erase(0,1); // LF + return name; + } + + std::string parse_certs_trust(std::string &response) { + sink_whitespace(response); + if(response.find("trust")!=0) throw new bad_format_exception(); + response.erase(0,6); + std::string trust=parse_quoted_string(response); + response.erase(0,1); // LF + return trust; + } + + void monotone_impl::parse_certs(const work_item &work,std::string &response) { + sink_whitespace(response); + if(response.find("key")==0) { + if(!count_six_lines(response)) return; // More data needed + // Cert stanza + std::string key=parse_certs_key(response); + std::string signature=parse_certs_sig(response); + std::string name=parse_certs_name(response); + std::string value=parse_certs_value(response); + std::string trust=parse_certs_trust(response); + response.erase(0,1); // LF + + cert the_stanza(key,signature,name,value,trust); + work.get_callback()->stanza_cert(the_stanza); + return; + } + // More data needed + + } + + std::string parse_keys_public_hash(std::string &response) { + sink_whitespace(response); + if(response.find("public_hash")!=0) throw new bad_format_exception(); + response.erase(0,12); + std::string hash=parse_quoted_hash(response); + response.erase(0,1); // LF + return hash; + } + + std::string parse_keys_private_hash(std::string &response) { + sink_whitespace(response); + if(response.find("private_hash")!=0) throw new bad_format_exception(); + response.erase(0,13); + std::string hash=parse_quoted_hash(response); + response.erase(0,1); // LF + return hash; + } + + void parse_keys_private_location(std::string &response,stringv &locs) { + sink_whitespace(response); + if(response.find("private_location")!=0) throw new bad_format_exception(); + response.erase(0,17); + parse_quoted_list(response,locs); + response.erase(0,1); // LF + return; + } + + void parse_keys_public_location(std::string &response,stringv &locs) { + sink_whitespace(response); + if(response.find("public_location")!=0) throw new bad_format_exception(); + response.erase(0,16); + parse_quoted_list(response,locs); + response.erase(0,1); // LF + return; + } + + void monotone_impl::parse_keys(const work_item &work,std::string &response) { + sink_whitespace(response); + if(response.find("name")==0) { + if(!count_six_lines(response) && + !count_five_lines(response) && + !count_four_lines(response)) return; // More data needed + // key_info stanza + std::string key_name=parse_keys_name(response); + std::string public_hash=parse_keys_public_hash(response); + std::string private_hash; + stringv private_location; + stringv public_location; + std::cerr << key_name << "\n"; + sink_whitespace(response); + if(response.find("private_hash")==0) { + private_hash.append(parse_keys_private_hash(response)); + } + parse_keys_public_location(response,public_location); + if(response.find("private_location")==0) { + parse_keys_private_location(response,private_location); + } + response.erase(0,1); // LF + key_info the_stanza(key_name,public_hash,private_hash,public_location,private_location); + work.get_callback()->stanza_key(the_stanza); + return; + } + // More data needed + } + + } +} + + --- contrib/crescendo/monotone.hh +++ contrib/crescendo/monotone.hh @@ -0,0 +1,253 @@ +#ifndef H_MONOTONE_H +#define H_MONOTONE_H + +#include +#include +#include "vocab.hh" +#include +#include +#include +#include "crescendo.hh" +#include + +using namespace boost; + +/** + * Main crescendo namespace + */ +namespace crescendo { + + /** + * Namespace for monotone specific crescendo classes + */ + namespace monotone { + + /** + * Default listener which implements no-op methods for all callback methods. + * This is the java style of doing this, but has the problem that + * implementing additional methods can break existing code. There may be + * a more C++ like way of doing this with functors + * + * Note that these methods are called on the worker thread in the + * monotone interface. If you don't want threaded goodness, see + * the adaptor classes in adaptor.hh which are pre-canned + * callbacks which enable you to be synchronous. + */ + class monotone_listener { + public: + monotone_listener() {}; + virtual ~monotone_listener() { }; + virtual void command_started() { }; + virtual void raw_data(const std::string &raw_data) { }; + virtual void stanza_revision_id(const revision_id &revision) { }; + virtual void stanza_revision_graph(const revision_id_list &id_list) { }; + + virtual void stanza_branch(const std::string &branch) { }; + virtual void stanza_tag(const tag &tag) { }; + virtual void stanza_file_status(const status &status) { }; + virtual void stanza_cert(const cert &cert) { }; + + virtual void stanza_manifest_dir(const manifest_dir &manifist_dir) { }; + virtual void stanza_manifest_file(const manifest_file &manifest_file) { }; + virtual void stanza_new_manifest(const manifest_id &manifest) { }; + virtual void stanza_old_revision(const revision_id &revision) { }; + virtual void stanza_delete(const fs::path &path) { }; + virtual void stanza_rename(const fs::path &from,const fs::path &to) { }; + virtual void stanza_add_dir(const fs::path &dir) { }; + virtual void stanza_add_file(const fs::path &file) { }; + virtual void stanza_patch(const fs::path &file,const file_id &from,const file_id &to) { }; + virtual void stanza_clear(const fs::path &file, const std::string &name) { }; + virtual void stanza_set(const fs::path &file, const std::string &name, const std::string &value) { }; + virtual void stanza_attribute(const file_attribute &attribute) { }; + virtual void file_contents(std::istream &source) { }; + virtual void stanza_option(const std::string &option) { }; + virtual void stanza_key(const key_info &key) { }; + virtual void stanza_file(const fs::path &file) { }; + virtual void command_complete() { }; + virtual void command_error(const std::string &error) { + // Default error handler. Override this if you want to + // handle errors youself + std::cerr << "MONOTONE ERROR: " << error << "\n"; + }; + + }; + + typedef class monotone_listener *MonotoneCallback; + static const stringv empty_args; + + /** + * Protocol class for an interface to monotone. An instance of this + * class should be obtained from a monotone_factory. + */ + class monotone { + + public: + virtual ~monotone() {}; + + /** + * Shutdown this interface to monotone and wait for it to close + */ + virtual void close_monotone() = 0; + + /** + * Purge the queue of any pending commands + */ + virtual void purge_queue() = 0; + + /** + * Get the version of the interface to monotone. TODO: Should this be here? + */ + virtual const std::string get_version() const = 0; + + /** + * Get the list of branches in the current monotone database + * @param callback a pointer to the callback object for the results + */ + virtual void branches(MonotoneCallback callback) = 0; + + /** + * Get the list of heads for the specified branch. + * Result is a callback for each revision_id which is a head on the branch + * + * @param branch the name of a branch in the current monotone database + * @param callback a pointer to the callback object for the results + */ + virtual void heads(const std::string &branch,MonotoneCallback callback) = 0; + + /** + * Get the list of revisions which are ancestors of the specified list of revisions. + * Result is a callback for each revision_id which is an ancestor of the specified revision ids + * @param id the list of identifiers for which ancestors should be found + * @param callback a pointer to the callback object for the results + */ + virtual void ancestors(const revision_id_list &id,MonotoneCallback callback) = 0; + + /** + * Get the list of revisions which are common ancestors of the specified list of revisions. + * Result is a callback for each revision_id which is an ancestor of the specified revision ids + * @param id the list of identifiers for which ancestors should be found + * @param callback a pointer to the callback object for the results + */ + virtual void common_ancestors(const revision_id_list &id,MonotoneCallback callback) = 0; + + /** + * Get the list of parents for the specified revision. + * Result is a callback for each revision_id which is a parent of the revision + * + * @param id the revision for which the parents should be returned + * @param callback a pointer to the callback object for the results + */ + virtual void parents(const revision_id &id,MonotoneCallback callback) = 0; + + /** + * Get the list of revisions which are descendents of the specified list of revisions. + * Result is a callback for each revision_id which is an descendent of the specified revision ids + * @param id the list of identifiers for which descendents should be found + * @param callback a pointer to the callback object for the results + */ + virtual void descendents(const revision_id_list &id,MonotoneCallback callback) = 0; + + /** + * Get the list of children for the specified revision. + * Result is a callback for each revision_id which is a child of the revision + * + * @param id the revision for which the children should be returned + * @param callback a pointer to the callback object for the results + */ + virtual void children(const revision_id &id,MonotoneCallback callback) = 0; + /* + virtual const shared_ptr< revision_graph > graph(MonotoneCallback callback) = 0; + */ + + /** + * Get the list of revisions in the input which are not an ancestor of some other revision in the input. + * Result is a callback for each revision_id which is not an ancestor of another revision in the input list + * @param id the input list of revisions + * @param callback a pointer to the callback object for the results + */ + virtual void erase_ancestors(const revision_id_list &id, MonotoneCallback callback) = 0; + + /** + * Topological sort of the input list. + * Result is a callback for each revision_id in topological order + * @param id the input list of revisions + * @param callback a pointer to the callback object for the results + */ + virtual void toposort(const revision_id_list &id,MonotoneCallback callback) = 0; + + /** + * Get the list of ancestors for new_id which are not also ancestors of old_id + * Result is a callback for each revision_id which is an ancestor of new_id but not an ancestor of old_id + * @param new_id the new revision identifier + * @param old_id a possibly empty list of old revisions + * @param callback a pointer to the callback object for the results + */ + virtual void ancestry_difference(const revision_id &new_id,const revision_id_list &old_id,MonotoneCallback backcallback) = 0; + + /** + * Get the list of revisions which are leaves of the graph + * Result is a callback for each revision_id which is a leaf of the graph + * + * @param callback a pointer to the callback object for the results + */ + virtual void leaves(MonotoneCallback callback) = 0; + virtual void tags(const std::string &pattern,MonotoneCallback callback) = 0; + virtual void certs(const revision_id &id, MonotoneCallback callback) = 0; + virtual void keys(MonotoneCallback callback) = 0; +/* + + virtual void select(revision_id_list &results,const selector &selector,MonotoneCallback callback) = 0; + virtual void inventory(status_list &results,MonotoneCallback callback) = 0; + + virtual const shared_ptr< const revision > get_revision(const revision_id &id,MonotoneCallback callback) = 0; + + */ + + /* + * Get the base revision of the workspace + * Result is a single callback for a revision_id + * + * @param callback a pointer to the callback object for the results + */ + virtual void get_base_revision_id(MonotoneCallback callback) = 0; + + /* + * Get the current revision of the workspace. + * The current revision is the revision which would be committed by + * and unrestricted commit on the current workspace. + * Result is a single callback for a revision_id + * + * @param callback a pointer to the callback object for the results + */ + virtual void get_current_revision_id(MonotoneCallback callback) = 0; + + /* + virtual const shared_ptr< const manifest> get_manifest_of(const revision_id &id,MonotoneCallback callback) = 0; + virtual void attributes(file_attribute_list &results,const file_id &file,MonotoneCallback callback) = 0; + virtual const shared_ptr< const content_difference > content_diff(const revision_id &first, const revision_id &second, const file_list &files,MonotoneCallback callback) = 0; + virtual std::istream get_file(const file_id &file_id,MonotoneCallback callback) = 0; + virtual std::istream get_file_of(const file_id &file_id,const revision_id &id) = 0; + virtual std::string get_option(const std::string &name,MonotoneCallback callback) = 0; + virtual void get_content_changed(revision_id_list &results,const revision_id &id,const file_id &file_id,MonotoneCallback callback) = 0; + virtual const fs::path get_corresponding_path(const revision_id &source,const fs::path &path, const revision_id &target,MonotoneCallback callback) = 0;*/ + }; + + /** + * Factory class which creates implementations of the monotone protocol + */ + class monotone_factory { + public: + monotone_factory() {}; + + /** + * Open a new connection to monotone and return the protocol interface. + * @param db the path to the monotone database to use + * @param working the path to the working directory to use + */ + const shared_ptr< monotone > get_monotone(fs::path db,fs::path working); + }; + + } +} + +#endif --- contrib/crescendo/monotone_impl.hh +++ contrib/crescendo/monotone_impl.hh @@ -0,0 +1,248 @@ +#ifndef H_MONOTONE_IMPL_H +#define H_MONOTONE_IMPL_H + +#include "monotone.hh" +#include + +typedef int fdx_t; + +namespace crescendo { + namespace monotone { + + static const std::string MONOTONE_EXE="mtn"; + static const std::string MTN_CMD_VERSION="interface_version"; + static const std::string cmd_text[] = { + "branches", + "heads", + "ancestors", + "common_ancestors", + "parents", + "descendents", + "children", + "erase_ancestors", + "toposort", + "ancestry_difference", + "leaves", + "get_base_revision_id", + "get_current_revision_id", + "tags", + "certs", + "keys" + }; + + // The version of the interface which we are expecting + static const std::string MTN_VERSION="4.0"; + + enum monotone_commands { + cmd_heads=1, + cmd_ancestors=2, + cmd_common_ancestors=3, + cmd_parents=4, + cmd_descendents=5, + cmd_children=6, + cmd_graph, + cmd_erase_ancestors=7, + cmd_toposort=8, + cmd_ancestry_difference=9, + cmd_leaves=10, + cmd_branches=0, + cmd_tags=13, + cmd_select, + cmd_inventory, + cmd_certs=14, + cmd_stdio, + cmd_get_revision, + cmd_get_base_revision_id=11, + cmd_get_current_revision_id=12, + cmd_get_manifest_of, + cmd_attributes, + cmd_content_diff, + cmd_get_file, + cmd_get_file_of, + cmd_get_option, + cmd_keys=15, + cmd_get_corresponding_path + }; + + /** + * One command on the queue + */ + class work_item { + + private: + monotone_commands cmd; + std::string command; + std::string command_options; + MonotoneCallback callback; + + public: + work_item() { }; + work_item(const work_item &other) { + cmd=other.cmd; + command=other.command; + command_options=other.command_options; + callback=other.callback; + }; + work_item(const monotone_commands r_cmd,const std::string &r_command,MonotoneCallback r_callback): cmd(r_cmd),command(r_command) { callback=r_callback; } + work_item(const monotone_commands r_cmd,const std::string &r_command,const std::string &r_options, MonotoneCallback r_callback): cmd(r_cmd),command(r_command),command_options(r_options) { callback=r_callback; } + + const std::string get_raw_command() const { return command; } ; + const std::string get_command_options() const { return command_options; }; + const MonotoneCallback get_callback() const { return callback; } + const monotone_commands get_cmd() const { return cmd; } + }; + + + class monotone_impl: public monotone { + + private: + /** + * Monotone automate response packet header + */ + struct packet_header { + int command_index; + int error_code; + bool last; + int packet_size; + }; + + fdx_t mtn_stdin; + fdx_t mtn_stdout; + + // Version of the monotone automation interface + std::string version; + + // Thread to manage communication with monotone + // We use a thread_group here for convenience, since thread is + // not copyable this makes initialisation easier + boost::thread_group worker; + + // Queue of work pending for the worker thread + std::vector< work_item > work_queue; + + // Lock for the work queue + boost::mutex work_queue_mutex; + + // Condition which is used to synchronise work between the worker + // thread and the boss threads + boost::condition work_queue_notify; + + // Special request flag to shutdown the worker + bool worker_should_exit; + + // special request flag to purge the queue after + // the current command completes + bool worker_purge_queue; + + // flag which indicates if the worker is currently processing + // a command + bool worker_busy; + + // Thread entry point for the worker thread + void do_work(); + + + const std::string make_command(const std::string cmd,const std::vector< std::string> args); + const std::string send_immediate(const std::string cmd,const std::vector< std::string> args); + void queue_command(const monotone_commands cmd,const std::vector< std::string > args, MonotoneCallback callback); + const void decode_packet_header(packet_header& header); + const void read_response(std::string &response); + void dispatch_job(const work_item &job); + void parse(const work_item &job,std::string &reponse); + void parse_branches(const work_item &job,std::string &response); + void parse_revisions(const work_item &job,std::string &response); + void parse_tags(const work_item &job,std::string &response); + void parse_certs(const work_item &job,std::string &response); + void parse_keys(const work_item &job,std::string &response); + + public: + static const int MONOTONE_MAX_PACKET=16383; // TODO: Verify this + + /** + * Destructor - shutdown and close communicaton with montone + * after current command completes + */ + virtual ~monotone_impl() { + close_monotone(); + } + + /** + * Close monotone after the current command completes, and + * wait for it to exit + */ + virtual void close_monotone() { + { + // Signal the worker thread to finish + boost::mutex::scoped_lock queue_guard(work_queue_mutex); + worker_should_exit=true; + work_queue_notify.notify_all(); + } + // Wait for the worker thread to finish + worker.join_all(); + close(mtn_stdout); + close(mtn_stdin); + } + + /** + * Clear all pending commands from the queue after the + * current command completes. Any attempts to push additional + * commands will busy-wait until the queue is clear + */ + virtual void purge_queue() { + boost::mutex::scoped_lock queue_guard(work_queue_mutex); + worker_purge_queue=true; + work_queue_notify.notify_all(); + } + + monotone_impl(fdx_t r_stdin,fdx_t r_stdout); + + /** + * Get the version of the monotone automate interface + */ + const std::string get_version() const { return version; }; + + /** + * List all the branches in the database + */ + virtual void branches(MonotoneCallback callback); + virtual void heads(const std::string &branch,MonotoneCallback callback); + virtual void ancestors(const revision_id_list &id,MonotoneCallback callback); + virtual void common_ancestors(const revision_id_list &id,MonotoneCallback callback); + virtual void parents(const revision_id &id,MonotoneCallback callback); + virtual void descendents(const revision_id_list &id,MonotoneCallback callback); + virtual void children(const revision_id &id,MonotoneCallback callback); + /* + virtual const shared_ptr< revision_graph > graph(MonotoneCallback callback); + */ + virtual void erase_ancestors(const revision_id_list &id, MonotoneCallback callback); + virtual void toposort(const revision_id_list &id,MonotoneCallback callback); + + virtual void ancestry_difference(const revision_id &new_id,const revision_id_list &old_id,MonotoneCallback backcallback); + virtual void leaves(MonotoneCallback callback); + + virtual void tags(const std::string &pattern,MonotoneCallback callback); + virtual void certs(const revision_id &id, MonotoneCallback callback); + virtual void keys(MonotoneCallback callback); + + /* + virtual void select(revision_id_list &results,const selector &selector,MonotoneCallback callback); + virtual void inventory(status_list &results,MonotoneCallback callback); + + virtual const shared_ptr< const revision > get_revision(const revision_id &id,MonotoneCallback callback); + */ + virtual void get_base_revision_id(MonotoneCallback callback); + virtual void get_current_revision_id(MonotoneCallback callback); + /* + virtual const shared_ptr< const manifest> get_manifest_of(const revision_id &id,MonotoneCallback callback); + virtual void attributes(file_attribute_list &results,const file_id &file,MonotoneCallback callback); + virtual const shared_ptr< const content_difference > content_diff(const revision_id &first, const revision_id &second, const file_list &files,MonotoneCallback callback); + virtual std::istream get_file(const file_id &file_id,MonotoneCallback callback); + virtual std::istream get_file_of(const file_id &file_id,const revision_id &id); + virtual std::string get_option(const std::string &name,MonotoneCallback callback); + virtual void get_content_changed(revision_id_list &results,const revision_id &id,const file_id &file_id,MonotoneCallback callback); + virtual const fs::path get_corresponding_path(const revision_id &source,const fs::path &path, const revision_id &target,MonotoneCallback callback);*/ + }; + } +} + +#endif