/* ==================================================================== * Copyright (c) 2003-2006, Martin Hauner * http://subcommander.tigris.org * * Subcommander is licensed as described in the file doc/COPYING, which * you should have received as part of this distribution. * ==================================================================== */ // sc #include "Client.h" #include "ErrorImpl.h" #include "Revision.h" #include "Notify.h" #include "WcStatus.h" #include "WcNotify.h" #include "DirEntry.h" #include "Info.h" #include "CommitInfo.h" #include "CommitItem.h" #include "CommitBaton.h" #include "StatusBaton.h" #include "CancelBaton.h" #include "BlameBaton.h" #include "LogBaton.h" #include "LogEntry.h" #include "BlameLine.h" #include "InfoBaton.h" #include "PropListItem.h" #include "PropGetItem.h" #include "ClientContext.h" #include "AuthPromptProvider.h" #include "DiffSummarizeBaton.h" #include "DiffSummarize.h" #include "VisualDiff.h" #include "VisualMerge.h" #include "Error.h" #include "util/apr.h" #include "util/String.h" #include "util/iconvstream.h" #include "subversion/repos_diff2.h" // svn #include #include #include #include // apr #include // sys #include #include using sc::Success; namespace svn { /////////////////////////////////////////////////////////////////////////////// // // svn_client_get_commit_log_t class CommitFns { public: static svn_error_t* get_commit_log( const char** logMsg, const char** tmpFile, apr_array_header_t* commitItems, void* baton, apr_pool_t* pool ) { CommitBaton* cb = static_cast(baton); CommitItems items; for( int i = 0; i < commitItems->nelts; i++ ) { svn_client_commit_item_t* item = (APR_ARRAY_IDX(commitItems,i,svn_client_commit_item_t*)); items.push_back( CommitItemPtr(new CommitItem(item)) ); } *logMsg = 0; *tmpFile = 0; sc::String log; sc::String file; bool commit = cb->getLogMsg( log, file, items ); if( commit ) { *logMsg = apr_pstrdup( pool, log.getStr() ); // \todo handle tmpFile } return SVN_NO_ERROR; } }; // /////////////////////////////////////////////////////////////////////////////// // // svn_cancel_func_t class CancelFns { public: static svn_error_t* check_cancel( void* baton ) { CancelBaton* cb = static_cast(baton); if( cb->isCanceled() ) { return svn_error_create( SVN_ERR_CANCELLED, 0, "canceled.." ); } return SVN_NO_ERROR; } }; // /////////////////////////////////////////////////////////////////////////////// // // svn_log_message_receiver_t class LogFns { public: static svn_error_t* log( void *baton, apr_hash_t* changedPaths, svn_revnum_t rev, const char* author, const char* date, const char* msg, apr_pool_t* pool ) { LogBaton* lb = static_cast(baton); Date tdate = 0; svn_time_from_cstring( &tdate, date, pool ); LogEntryPtr entry = LogEntryPtr( new LogEntry(rev,tdate,sc::String(author),sc::String(msg)) ); if( changedPaths ) { apr_hash_index_t *hi; for( hi = apr_hash_first(pool,changedPaths); hi; hi = apr_hash_next(hi) ) { const char* key; svn_log_changed_path_t* item; apr_hash_this( hi, (const void**)&key, NULL, (void**)&item ); LogEntry::Path path( key, item ); entry->addPath( path ); } } lb->receiveMessage( entry ); return SVN_NO_ERROR; } }; // /////////////////////////////////////////////////////////////////////////////// // // svn_wc_notify_func_t class NotifyFns { public: static void notify( void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool ) { NotifyBaton* nb = static_cast(baton); WcNotifyPtr ny( new WcNotify(notify) ); nb->notify(ny); } }; // /////////////////////////////////////////////////////////////////////////////// // // svn_client_ctx_t, svn_auth_baton_t, prompt providers.. class AuthFns { public: static AuthPromptProvider* ab_cast( void* baton ) { return static_cast(baton); } // svn_auth_simple_prompt_func_t static svn_error_t* simple_prompt( svn_auth_cred_simple_t **cred_p, void *baton, const char *realm, const char *username, svn_boolean_t may_save, apr_pool_t *pool ) { AuthPromptProvider* auth = ab_cast(baton); AuthPromptProvider::simpleCredentials cred; bool result = auth->simplePrompt( realm, username, may_save != 0, cred ); if( ! result ) { return svn_error_create( SVN_ERR_AUTHN_CREDS_UNAVAILABLE, 0, "simple prompt" ); } svn_auth_cred_simple_t *ret = (svn_auth_cred_simple_t*)apr_pcalloc( pool, sizeof(*ret) ); // TODO remove when fixed in subversion ret->username = apr_pstrdup( pool, (const char*)cred._username ? (const char*)cred._username : "" ); ret->password = apr_pstrdup( pool, (const char*)cred._password ? (const char*)cred._password : "" ); ret->may_save = cred._save; *cred_p = ret; return SVN_NO_ERROR; } // svn_auth_username_prompt_func_t static svn_error_t* username_prompt( svn_auth_cred_username_t **cred_p, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool ) { // when is this called? // sample implementation in subversion/cmdline/prompt.c return SVN_NO_ERROR; } // svn_auth_ssl_server_trust_prompt_func_t static svn_error_t* ssl_server_trust_prompt( svn_auth_cred_ssl_server_trust_t **cred_p, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *cert_info, svn_boolean_t may_save, apr_pool_t *pool ) { AuthPromptProvider* auth = ab_cast(baton); AuthPromptProvider::sslServerCertInfo certinfo; certinfo.hostname = cert_info->hostname; certinfo.fingerprint = cert_info->fingerprint; certinfo.validFrom = cert_info->valid_from; certinfo.validUntil = cert_info->valid_until; certinfo.issuerDName = cert_info->issuer_dname; certinfo.asciiCert = cert_info->ascii_cert; AuthPromptProvider::sslServerTrustCredentials cred; bool result = auth->sslServerTrustPrompt( realm, failures, certinfo, may_save != 0, cred ); if( ! result ) { return svn_error_create( SVN_ERR_AUTHN_CREDS_UNAVAILABLE, 0, "ssl server trust prompt" ); } svn_auth_cred_ssl_server_trust_t *ret = (svn_auth_cred_ssl_server_trust_t*)apr_pcalloc( pool, sizeof(*ret) ); ret->accepted_failures = cred._acceptedFailures; ret->may_save = cred._save; *cred_p = ret; return SVN_NO_ERROR; } // svn_auth_ssl_client_cert_prompt_func_t static svn_error_t* ssl_client_cert_prompt( svn_auth_cred_ssl_client_cert_t **cred_p, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool ) { // sample implementation in subversion/cmdline/prompt.c return SVN_NO_ERROR; } // svn_auth_ssl_client_cert_pw_prompt_func_t static svn_error_t* ssl_client_cert_pw_prompt( svn_auth_cred_ssl_client_cert_pw_t **cred_p, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool ) { // sample implementation in subversion/cmdline/prompt.c return SVN_NO_ERROR; } }; // /////////////////////////////////////////////////////////////////////////////// // // *svn_wc_status_func2_t, status_baton class StatusFns { public: StatusFns( StatusBaton* baton ) : _baton(baton) { } static StatusFns* sf_cast( void* v ) { return static_cast(v); } static void status( void* baton, const char* path, svn_wc_status2_t* state ) { StatusFns* fns = sf_cast(baton); if( state ) { WcStatusPtr wcs( new WcStatus( sc::String(path), state ) ); fns->getBaton()->status( path, wcs ); } else { fns->getBaton()->status( path, WcStatusPtr() ); } } StatusBaton* getBaton() const { return _baton; } private: StatusBaton* _baton; }; // /////////////////////////////////////////////////////////////////////////////// // class BlameFns { public: BlameFns( BlameBaton* baton ) : _baton(baton) { } static BlameFns* bf_cast( void* v ) { return static_cast(v); } static svn_error_t* receive( void* baton, apr_int64_t lineNo, svn_revnum_t revision, const char* author, const char* date, const char* line, apr_pool_t* pool ) { BlameFns* fns = bf_cast(baton); Date tdate = 0; if( date ) { svn_time_from_cstring( &tdate, date, pool ); } BlameLine* bline = new BlameLine( lineNo, revision, sc::String(author), tdate, sc::String(line) ); fns->getBaton()->receive(bline); return SVN_NO_ERROR; } BlameBaton* getBaton() const { return _baton; } private: BlameBaton* _baton; }; // /////////////////////////////////////////////////////////////////////////////// // // *svn_diff_summarize_func_t, diff_summarize_baton class DiffSummarizeFns { public: DiffSummarizeFns( DiffSummarizeBaton* baton ) : _baton(baton) { } static DiffSummarizeFns* dsf_cast( void* v ) { return static_cast(v); } static svn_error_t* summarize( const svn_client_diff_summarize_t *diff, void *baton, apr_pool_t *pool ) { DiffSummarizeFns* fns = dsf_cast(baton); if( diff ) { DiffSummarizePtr dp( new DiffSummarize(diff) ); fns->getBaton()->summarize(dp); } return SVN_NO_ERROR; } DiffSummarizeBaton* getBaton() const { return _baton; } private: DiffSummarizeBaton* _baton; }; // /////////////////////////////////////////////////////////////////////////////// // class InfoFns { public: InfoFns( InfoBaton* baton ) : _baton(baton) { } static InfoFns* if_cast( void* v ) { return static_cast(v); } typedef svn_error_t *(*svn_info_receiver_t) (void *baton, const char *path, const svn_info_t *info, apr_pool_t *pool); static svn_error_t* receive( void* baton, const char* path, const svn_info_t* info, apr_pool_t* pool ) { InfoFns* fns = if_cast(baton); InfoPtr infp( new Info( sc::String(path), info ) ); fns->getBaton()->info(infp); return SVN_NO_ERROR; } InfoBaton* getBaton() const { return _baton; } private: InfoBaton* _baton; }; // /////////////////////////////////////////////////////////////////////////////// // ClientContext::ClientContext( AuthPromptProvider* pp, CancelBaton* cb, NotifyBaton* nb, CommitBaton* cob, const sc::String& diff, const sc::String& merge ) : _authProv(pp), _diff(diff), _merge(merge) { // see subversion/clients/commandline/main.c for what is going on here.. _pool = apr::createPool(0); svn_error_t* err; // make sure the system-wide subversion configuration exits. err = svn_config_ensure( 0, _pool ); // \todo handle error err = svn_client_create_context( &_context, _pool ); // \todo handle error // load system wide configuration err = svn_config_get_config( &(_context->config), 0, _pool ); // \todo handle error // log message callback _context->log_msg_func = CommitFns::get_commit_log; _context->log_msg_baton = cob; // set up our cancellation support _context->cancel_func = CancelFns::check_cancel; _context->cancel_baton = cb; // set up notify callback _context->notify_func2 = NotifyFns::notify; _context->notify_baton2 = nb; // authentication //svn_boolean_t store_password_val = TRUE; svn_auth_provider_object_t* provider; svn_auth_baton_t* ab; // the whole list of registered providers apr_array_header_t *providers = apr_array_make( _pool, 10, sizeof(svn_auth_provider_object_t*) ); // The main disk-caching auth providers, for both // 'username/password' creds and 'username' creds. svn_client_get_simple_provider( &provider, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; svn_client_get_username_provider( &provider, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; // ... server-cert, client-cert, and client-cert-password providers. svn_client_get_ssl_server_trust_file_provider( &provider, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; svn_client_get_ssl_client_cert_file_provider( &provider, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; svn_client_get_ssl_client_cert_pw_file_provider( &provider, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; // Two basic prompt providers: username/password, and just username. svn_client_get_simple_prompt_provider( &provider, AuthFns::simple_prompt, pp, 2, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; svn_client_get_username_prompt_provider( &provider, AuthFns::username_prompt, pp, 2, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; // Three ssl prompt providers: server-certs, client-certs, and // client-cert-passphrases. svn_client_get_ssl_server_trust_prompt_provider( &provider, AuthFns::ssl_server_trust_prompt, pp, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; svn_client_get_ssl_client_cert_prompt_provider( &provider, AuthFns::ssl_client_cert_prompt, pp, 2, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; svn_client_get_ssl_client_cert_pw_prompt_provider( &provider, AuthFns::ssl_client_cert_pw_prompt, pp, 2, _pool ); APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider; // build the authentication baton svn_auth_open( &ab, providers, _pool ); _context->auth_baton = ab; } ClientContext::~ClientContext() { delete (CancelBaton*)_context->cancel_baton; delete (NotifyBaton*)_context->notify_baton; delete (CommitBaton*)_context->log_msg_baton; delete _authProv; apr::destroyPool(_pool); } CancelBaton* ClientContext::getCancelBaton() const { return (CancelBaton*)_context->cancel_baton; } const sc::String& ClientContext::getDiffCmd() const { return _diff; } const sc::String& ClientContext::getMergeCmd() const { return _merge; } // /////////////////////////////////////////////////////////////////////////////// /** * create a subversion Client object. The Client object takes ownership of * the ClientContext pointer. * * \param context the ClientContext. * \param pool an optional root pool. */ Client::Client( ClientContext* context, apr_pool_t *pool ) { _pool = apr::createPool(pool); _context = context; } Client::~Client() { delete _context; apr::destroyPool(_pool); } sc::Error* Client::checkout( Revnumber* resultRev, const sc::String& url, const sc::String& path, const Revision& peg, const Revision& rev, bool recurse ) { apr::Pool pool( _pool ); const char* curl = preparePathOrUrl( url, pool ); const char* cpath = preparePathOrUrl( path, pool ); svn_revnum_t resrev = 0; svn_opt_revision_t svnrev; svn_opt_revision_t svnpeg; convertRevision( &rev, &svnrev ); convertRevision( &peg, &svnpeg ); svn_error_t* err = svn_client_checkout2( &resrev, curl, cpath, &svnpeg, &svnrev, recurse, false /* todo external*/, _context->_context, pool ); if( err ) { return wrapError(err); } *resultRev = resrev; return Success; } sc::Error* Client::update( Revnumber* resultRev, const sc::String& path, const Revision& rev, bool recurse ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); svn_revnum_t resrev = 0; svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); svn_error_t* err = svn_client_update( &resrev, cpath, &svnrev, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } *resultRev = resrev; return Success; } sc::Error* Client::switchx( Revnumber* resultRev, const sc::String& path, const sc::String& url, const Revision& rev, bool recurse ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); const char* curl = preparePathOrUrl( url, pool ); svn_revnum_t resrev = 0; svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); svn_error_t* err = svn_client_switch( &resrev, cpath, curl, &svnrev, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } *resultRev = resrev; return Success; } sc::Error* Client::status( Revnumber* resultRev, const sc::String& path, const Revision& rev, StatusBaton *baton, bool recurse, bool all, bool update, bool ignore ) { apr::Pool pool( _pool ); const char *cpath = preparePathOrUrl( path, pool ); svn_revnum_t resrev = 0; svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); StatusFns fns(baton); svn_error_t* err = svn_client_status2( &resrev, cpath, &svnrev, StatusFns::status, &fns, recurse, all, update, ignore, false /*todo external*/, _context->_context, pool ); if( err ) { return wrapError(err); } *resultRev = resrev; return Success; } sc::Error* Client::commit( CommitInfo& resultInfo, const Paths& paths, bool recurse, bool keepLocks ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)paths.size(), sizeof(char*) ); for( Paths::const_iterator it = paths.begin(); it != paths.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_client_commit_info_t* commitInfo = 0; svn_error_t* err = svn_client_commit2( &commitInfo, patharr, recurse, keepLocks, _context->_context, pool ); if( err ) { return wrapError(err); } resultInfo = commitInfo; return Success; } sc::Error* Client::ls( const sc::String& pathOrUrl, const Revision& rev, bool recurse, DirEntries& entries ) { apr::Pool pool(_pool); const char *cpath = preparePathOrUrl( pathOrUrl, pool ); apr_hash_t* hash; svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); svn_error_t* err = svn_client_ls( &hash, cpath, &svnrev, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } apr_hash_index_t* hi; for( hi = apr_hash_first(_pool,hash); hi; hi = apr_hash_next(hi) ) { const char* key; apr_ssize_t klen; svn_dirent_t* val; apr_hash_this( hi, (const void**)&key, &klen, (void**)&val ); sc::String name = pathOrUrl; name += "/"; name += sc::String(key,klen); entries.push_back( DirEntryPtr(new DirEntry(name,val)) ); } return Success; } sc::Error* Client::mkdir( CommitInfo& resultInfo, const Paths& pathsOrUrls ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(), sizeof(char*) ); for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_client_commit_info_t* commitInfo = 0; svn_error_t* err = svn_client_mkdir( &commitInfo, patharr, _context->_context, pool ); if( err ) { return wrapError(err); } resultInfo = commitInfo; return Success; } #if 0 Error*Client::add( const sc::String& path, bool recurse, bool force ) { apr::Pool pool( _pool ); const char* cpath = svn_path_canonicalize( path, pool ); svn_error_t* err = svn_client_add2( cpath, recurse, force, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } #endif sc::Error* Client::add( const Paths& paths, bool recurse, bool force ) { apr::Pool pool( _pool ); for( Paths::const_iterator it = paths.begin(); it != paths.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); svn_error_t* err = svn_client_add2( cpath, recurse, force, _context->_context, pool ); if( err ) { return wrapError(err); } } return Success; } sc::Error* Client::deletex( CommitInfo& resultInfo, const Paths& pathsOrUrls, bool force ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(), sizeof(char*) ); for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_client_commit_info_t* commitInfo = 0; svn_error_t* err = svn_client_delete( &commitInfo, patharr, force, _context->_context, pool ); if( err ) { return wrapError(err); } resultInfo = commitInfo; return Success; } sc::Error* Client::revert( const Paths& paths, bool recurse ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)paths.size(), sizeof(char*) ); for( Paths::const_iterator it = paths.begin(); it != paths.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_error_t* err = svn_client_revert( patharr, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::import( CommitInfo& resultInfo, const sc::String& path, const sc::String& url, bool recurse ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); const char* curl = preparePathOrUrl( url, pool ); svn_client_commit_info_t* commitInfo = 0; svn_error_t* err = svn_client_import( &commitInfo, cpath, curl, ! recurse, _context->_context, _pool ); if( err ) { return wrapError(err); } resultInfo = commitInfo; return Success; } sc::Error* Client::blame( const sc::String& pathOrUrl, const Revision& begin, const Revision& end, BlameBaton* baton ) { apr::Pool pool(_pool); const char *cpath = preparePathOrUrl( pathOrUrl, pool ); svn_opt_revision_t revBegin; convertRevision( &begin, &revBegin ); svn_opt_revision_t revEnd; convertRevision( &end, &revEnd ); BlameFns fns(baton); svn_error_t* err = svn_client_blame( cpath, &revBegin, &revEnd, BlameFns::receive, &fns, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::cleanup( const sc::String& path ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); svn_error_t* err = svn_client_cleanup( cpath, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::relocate( const sc::String& path, const sc::String& fromUrl, const sc::String& toUrl, bool recurse ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); const char* cfromUrl = preparePathOrUrl( fromUrl, pool ); const char* ctoUrl = preparePathOrUrl( toUrl, pool ); svn_error_t* err = svn_client_relocate( cpath, cfromUrl, ctoUrl, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::resolved( const sc::String& path, bool recurse ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); svn_error_t* err = svn_client_resolved( cpath, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::log( const Paths& pathsOrUrls, const Revision& start, const Revision& stop, int limit, bool discoverChangedPaths, bool strictNodeHistory, LogBaton* baton ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(), sizeof(char*) ); for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_opt_revision_t crev1; svn_opt_revision_t crev2; convertRevision( &start, &crev1 ); convertRevision( &stop, &crev2 ); svn_error_t* err = svn_client_log2( patharr, &crev1, &crev2, limit, discoverChangedPaths, strictNodeHistory, &LogFns::log, baton, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::diff( const sc::String& pathOrUrl1, const Revision& rev1, const sc::String& pathOrUrl2, const Revision& rev2, bool recurse, bool ancestry, bool deleted, bool patch, sc::String& patchFile ) { apr::Pool pool( _pool ); apr_array_header_t* dopts = apr_array_make( pool, 0, sizeof(char*) ); const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool ); const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); apr_file_t *outfile; apr_file_t *errfile; apr_status_t status; const char* outfileName; apr_time_t now = apr_time_now(); char* nowstr = apr_psprintf( pool, "%" APR_TIME_T_FMT, now ); apr_int32_t flags = APR_CREATE|APR_WRITE; if( ! patch ) { flags |= APR_DELONCLOSE; } { // todo error handling.. //char* err = apr_strerror( status, errbuf, 255 ); //assert( status == APR_SUCCESS ); const char* tempdir = 0; status = apr_temp_dir_get( &tempdir, pool ); char* tempout = apr_pstrcat( pool, tempdir, "/sc_stdout.log.", nowstr, NULL ); status = apr_file_open( &outfile, tempout, flags, APR_OS_DEFAULT, pool ); outfileName = tempout; } { const char* tempdir = 0; status = apr_temp_dir_get( &tempdir, pool ); char* temperr = apr_pstrcat( _pool, tempdir, "/sc_stderr.log.", nowstr, NULL ); status = apr_file_open( &errfile, temperr, flags, APR_OS_DEFAULT, pool ); } svn_error_t* err = svn_client_diff( dopts, cpath1, &crev1, cpath2, &crev2, recurse, !ancestry, !deleted, outfile, errfile, _context->_context, pool ); apr_file_close(outfile); apr_file_close(errfile); if( err ) { return wrapError(err); } patchFile = outfileName; return Success; } sc::Error* Client::diff( const sc::String& srcPathOrUrl, const Revision& rev1, const Revision& rev2, const Revision& peg, bool recurse, bool ancestry, bool deleted, bool patch, sc::String& patchFile ) { apr::Pool pool( _pool ); apr_array_header_t* dopts = apr_array_make( pool, 0, sizeof(char*) ); const char* cpath = preparePathOrUrl( srcPathOrUrl, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; svn_opt_revision_t cpeg; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); convertRevision( &peg, &cpeg ); apr_file_t *outfile; apr_file_t *errfile; apr_status_t status; const char* outfileName; apr_time_t now = apr_time_now(); char* nowstr = apr_psprintf( pool, "%" APR_TIME_T_FMT, now ); apr_int32_t flags = APR_CREATE|APR_WRITE; if( ! patch ) { flags |= APR_DELONCLOSE; } { const char* tempdir = 0; status = apr_temp_dir_get( &tempdir, pool ); char* tempout = apr_pstrcat( pool, tempdir, "/sc_stdout.log.", nowstr, NULL ); status = apr_file_open( &outfile, tempout, flags, APR_OS_DEFAULT, pool ); outfileName = tempout; } { const char* tempdir = 0; status = apr_temp_dir_get( &tempdir, pool ); char* temperr = apr_pstrcat( _pool, tempdir, "/sc_stderr.log.", nowstr, NULL ); status = apr_file_open( &errfile, temperr, flags, APR_OS_DEFAULT, pool ); } svn_error_t* err = svn_client_diff_peg( dopts, cpath, &cpeg, &crev1, &crev2, recurse, !ancestry, !deleted, outfile, errfile, _context->_context, pool ); apr_file_close(outfile); apr_file_close(errfile); if( err ) { return wrapError(err); } patchFile = outfileName; return Success; } sc::Error* Client::diffSummarize( const sc::String& pathOrUrl1, const Revision& rev1, const sc::String& pathOrUrl2, const Revision& rev2, DiffSummarizeBaton* baton, bool recurse, bool ancestry ) { apr::Pool pool( _pool ); const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool ); const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); DiffSummarizeFns fns(baton); svn_error_t* err = svn_client_diff_summarize( cpath1, &crev1, cpath2, &crev2, recurse, !ancestry, DiffSummarizeFns::summarize, &fns, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::diffSummarize( const sc::String& srcPathOrUrl, const Revision& rev1, const Revision& rev2, const Revision& peg, DiffSummarizeBaton* baton, bool recurse, bool ancestry ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( srcPathOrUrl, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; svn_opt_revision_t cpeg; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); convertRevision( &peg, &cpeg ); DiffSummarizeFns fns(baton); svn_error_t* err = svn_client_diff_summarize_peg( cpath, &cpeg, &crev1, &crev2, recurse, !ancestry, DiffSummarizeFns::summarize, &fns, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::visualDiff( const sc::String& pathOrUrl1, const Revision& rev1, const sc::String& pathOrUrl2, const Revision& rev2 ) { apr::Pool pool( _pool ); const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool ); const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); VisualDiff vdiff( _context->_context, _context->getDiffCmd(), pool ); svn_error_t* err = vdiff.run( cpath1, &crev1, cpath2, &crev2 ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::visualDiff( const sc::String& srcPathOrUrl, const Revision& rev1, const Revision& rev2, const Revision& peg ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( srcPathOrUrl, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; svn_opt_revision_t cpeg; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); convertRevision( &peg, &cpeg ); VisualDiff vdiff( _context->_context, _context->getDiffCmd(), pool ); svn_error_t* err = vdiff.run( cpath, &crev1, &crev2, &cpeg ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::merge( const sc::String& pathOrUrl1, const Revision& rev1, const sc::String& pathOrUrl2, const Revision& rev2, const sc::String& targetPath, bool recurse, bool ancestry, bool force, bool dryRun ) { apr::Pool pool( _pool ); const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool ); const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); const char* ctarget = svn_path_canonicalize( targetPath, pool ); svn_error_t* err = svn_client_merge( cpath1, &crev1, cpath2, &crev2, ctarget, recurse, !ancestry, force, dryRun, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::merge( const sc::String& srcPathOrUrl, const Revision& rev1, const Revision& rev2, const Revision& peg, const sc::String& targetPath, bool recurse, bool ancestry, bool force, bool dryRun ) { apr::Pool pool( _pool ); const char* cpath1 = preparePathOrUrl( srcPathOrUrl, pool ); svn_opt_revision_t crev1; svn_opt_revision_t crev2; svn_opt_revision_t cpeg; convertRevision( &rev1, &crev1 ); convertRevision( &rev2, &crev2 ); convertRevision( &peg, &cpeg ); const char* ctarget = preparePathOrUrl( targetPath, pool ); svn_error_t* err = svn_client_merge_peg( cpath1, &crev1, &crev2, &cpeg, ctarget, recurse, !ancestry, force, dryRun, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::visualMerge( const WcStatusPtr file ) { apr::Pool pool( _pool ); VisualMerge vmerge( _context->_context, _context->getMergeCmd(), pool ); svn_error_t* err = vmerge.run( file ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::copy( CommitInfo& resultInfo, const Paths& srcPathsOrUrls, const Revision& srcRev, const sc::String& dstPathOrUrl ) { apr::Pool pool( _pool ); for( Paths::const_iterator it = srcPathsOrUrls.begin(); it != srcPathsOrUrls.end(); it++ ) { const char* csrc = preparePathOrUrl( *it, pool ); svn_opt_revision_t crev; convertRevision( &srcRev, &crev ); const char* cdst = preparePathOrUrl( dstPathOrUrl, pool ); svn_client_commit_info_t* commitInfo = 0; svn_error_t* err = svn_client_copy( &commitInfo, csrc, &crev, cdst, _context->_context, pool ); if( err ) { return wrapError(err); } resultInfo = commitInfo; } return Success; } sc::Error* Client::move( CommitInfo& resultInfo, const Paths& srcPathsOrUrls, const Revision& srcRev, const sc::String& dstPathOrUrl, bool force ) { apr::Pool pool( _pool ); for( Paths::const_iterator it = srcPathsOrUrls.begin(); it != srcPathsOrUrls.end(); it++ ) { const char* csrc = preparePathOrUrl( *it, pool ); svn_opt_revision_t crev; convertRevision( &srcRev, &crev ); const char* cdst = preparePathOrUrl( dstPathOrUrl, pool ); svn_client_commit_info_t* commitInfo = 0; svn_error_t* err = svn_client_move( &commitInfo, csrc, &crev, cdst, force, _context->_context, pool ); if( err ) { return wrapError(err); } resultInfo = commitInfo; } return Success; } sc::Error* Client::exportx( Revnumber* resultRev, const sc::String& srcPathOrUrl, const Revision& srcRev, const sc::String& dstPath, bool force, const sc::String& eol ) { apr::Pool pool( _pool ); const char *csrc = preparePathOrUrl( srcPathOrUrl, pool ); const char *cdst = preparePathOrUrl( dstPath, pool ); svn_revnum_t resrev = 0; svn_opt_revision_t svnrev; convertRevision( &srcRev, &svnrev ); svn_error_t* err = svn_client_export2( &resrev, csrc, cdst, &svnrev, force, eol, _context->_context, pool ); if( err ) { return wrapError(err); } *resultRev = resrev; return Success; } class MemoryStream { public: MemoryStream( apr::Pool& pool ) { _stream = svn_stream_create( this, pool ); svn_stream_set_read( _stream, read_fn ); svn_stream_set_write( _stream, write_fn ); svn_stream_set_close( _stream, close_fn ); } static svn_error_t* read_fn( void* baton, char* buffer, apr_size_t* len ) { return SVN_NO_ERROR; } static svn_error_t* write_fn( void* baton, const char* buffer, apr_size_t* len ) { MemoryStream* stream = static_cast(baton); stream->_str += sc::String( buffer, *len ); return SVN_NO_ERROR; } static svn_error_t* close_fn( void* baton ) { return SVN_NO_ERROR; } operator svn_stream_t*() const { return _stream; } const sc::String& get() const { return _str; } private: svn_stream_t* _stream; sc::String _str; }; sc::Error* Client::cat( sc::String& out, const sc::String& pathOrUrl, const Revision& rev ) { apr::Pool pool( _pool ); const char *cpath = preparePathOrUrl( pathOrUrl, pool ); svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); MemoryStream stream( pool ); svn_error_t* err = svn_client_cat( stream, cpath, &svnrev, _context->_context, pool ); if( err ) { return wrapError(err); } // convert the file that was just converted by svn_client_cat from utf8 // to the local encoding back to utf8... sc::String tmp = stream.get(); std::strstream s1( (char*)tmp.getStr(), (std::streamsize)tmp.getByteCnt(), std::ios_base::binary|std::ios_base::in ); iconvistream s2( s1, "*", "utf-8" ); char buf[2] = {}; while( ! s2.eof() ) { s2.read( buf, sizeof(buf)-1 ); out += buf; } // return Success; } sc::Error* Client::proplist( PropListItems& items, const sc::String& pathOrUrl, const Revision& rev, bool recurse ) { apr::Pool pool( _pool ); const char *cpath = preparePathOrUrl( pathOrUrl, pool ); apr_array_header_t* propItems; svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); svn_error_t* err = svn_client_proplist( &propItems, cpath, &svnrev, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } for( int i = 0; i < propItems->nelts; i++ ) { svn_client_proplist_item_t* item = (APR_ARRAY_IDX(propItems,i,svn_client_proplist_item_t*)); items.push_back( PropListItemPtr(new PropListItem(item)) ); } return Success; } sc::Error* Client::propget( PropGetItems& items, const sc::String& propName, const sc::String& pathOrUrl, const Revision& rev, bool recurse ) { apr::Pool pool( _pool ); const char *cpath = preparePathOrUrl( pathOrUrl, pool ); svn_opt_revision_t svnrev; convertRevision( &rev, &svnrev ); apr_hash_t* prophash = 0; svn_error_t* err = svn_client_propget( &prophash, propName, cpath, &svnrev, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } apr_hash_index_t* hi; for( hi = apr_hash_first(_pool,prophash); hi; hi = apr_hash_next(hi) ) { const char* key; apr_ssize_t klen; svn_string_t* val; apr_hash_this( hi, (const void**)&key, &klen, (void**)&val ); items.push_back( PropGetItemPtr( new PropGetItem(sc::String(key,klen),sc::String(val->data))) ); } return Success; } sc::Error* Client::propset( const sc::String& propName, const sc::String& propVal, const sc::String& path, bool recurse ) { apr::Pool pool( _pool ); const char* cpath = preparePathOrUrl( path, pool ); svn_string_t* cval = propVal.getStr() ? svn_string_create( propVal, pool ) : 0; svn_error_t* err = svn_client_propset( propName, cval, cpath, recurse, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::propsetrev( const sc::String& propName, const sc::String& propVal, const sc::String& pathOrUrl, const Revision& srcRev, Revnumber* resultRev, bool force ) { apr::Pool pool( _pool ); const char* url = 0; svn_error_t* err = svn_client_url_from_path( &url, pathOrUrl, pool ); if( err ) { return wrapError(err); } svn_revnum_t resultrev = 0; const char* curl = preparePathOrUrl( url, pool ); svn_string_t* cval = svn_string_create( propVal, pool ); svn_opt_revision_t svnrev; convertRevision( &srcRev, &svnrev ); err = svn_client_revprop_set( propName, cval, curl, &svnrev, &resultrev, force, _context->_context, pool ); if( err ) { return wrapError(err); } *resultRev = resultrev; return Success; } sc::Error* Client::lock( const Paths& pathsOrUrls, const sc::String& comment, bool stealLocks ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(), sizeof(char*) ); for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_error_t* err = svn_client_lock( patharr, comment.getStr(), stealLocks, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::unlock( const Paths& pathsOrUrls, bool breakLocks ) { apr::Pool pool( _pool ); apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(), sizeof(char*) ); for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ ) { const char* cpath = preparePathOrUrl( *it, pool ); APR_ARRAY_PUSH( patharr, const char* ) = cpath; } svn_error_t* err = svn_client_unlock( patharr, breakLocks, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::info( const sc::String& pathOrUrl, const Revision& rev, const Revision& peg, InfoBaton* baton, bool recurse ) { apr::Pool pool( _pool ); const char *cpath = preparePathOrUrl( pathOrUrl, pool ); svn_opt_revision_t svnrev; svn_opt_revision_t pegrev; convertRevision( &rev, &svnrev ); convertRevision( &peg, &pegrev ); InfoFns ib(baton); svn_error_t* err = svn_client_info( cpath, &pegrev, &svnrev, InfoFns::receive, &ib, recurse, _context->_context, pool ); if( err ) { return wrapError(err); } return Success; } sc::Error* Client::details( Revnumber* resultRev, sc::String& root, const sc::String& pathOrUrl ) { class Ra { public: static svn_error_t* open_tmp_file( apr_file_t **f, void *callback_baton, apr_pool_t *pool ) { const char* path; const char* ignore; SVN_ERR( svn_io_temp_dir( &path, pool ) ); path = svn_path_join( path, "tempfile", pool ); SVN_ERR( svn_io_open_unique_file( f, &ignore, path, ".tmp", true, pool ) ); return SVN_NO_ERROR; } }; apr::Pool pool( _pool ); const char* url; void* raSession; void* raBaton; svn_ra_plugin_t* raLib; svn_ra_callbacks_t* raCb = (svn_ra_callbacks_t*)apr_pcalloc( pool, sizeof(*raCb)); raCb->open_tmp_file = Ra::open_tmp_file; raCb->auth_baton = _context->_context->auth_baton; const char *resultUrl; *resultRev = SVN_INVALID_REVNUM; SC_SVN_ERR( svn_client_url_from_path( &url, pathOrUrl, pool ) ); SC_SVN_ERR( svn_ra_init_ra_libs( &raBaton, pool ) ); SC_SVN_ERR( svn_ra_get_ra_library( &raLib, raBaton, url, pool ) ); SC_SVN_ERR( raLib->open( &raSession, url, raCb, 0, _context->_context->config, pool) ); SC_SVN_ERR( raLib->get_latest_revnum( raSession, resultRev, pool ) ); SC_SVN_ERR( raLib->get_repos_root( raSession, &resultUrl, pool ) ); root = sc::String(resultUrl); return Success; } bool Client::isWorkingCopy( const sc::String& path ) { apr::Pool pool; svn_wc_adm_access_t* adm; // handle an empty path, svn_path_canonicalize crashes when it gets an // empty string. if( path.isEmpty() ) { return false; } const char* cpath = preparePathOrUrl( path, pool ); svn_error_t* err = svn_wc_adm_open2( &adm, NULL, cpath, false, 0, pool ); if( err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY ) { return false; } return true; } #if 0 /** * \brief get the revision number of 'head' on the given path or url. * * \param resultRev variable to store the 'head' revision number of pathOrUrl in. * \param pathOrUrl the utf-8 encoded path or url we want the 'head' revision from. * \return the error object or SVN_SUCCESS. */ // based on libsvn_client/cat.c:svn_client_cat Error* Client::getHeadRevision( Revnumber* resultRev, const sc::String& pathOrUrl ) { apr::Pool pool( _pool ); const char* url; void* raSession; void* raBaton; svn_ra_plugin_t* raLib; *resultRev = SVN_INVALID_REVNUM; SC_SVN_ERR( svn_client_url_from_path( &url, pathOrUrl, pool ) ); SC_SVN_ERR( svn_ra_init_ra_libs( &raBaton, pool ) ); SC_SVN_ERR( svn_ra_get_ra_library( &raLib, raBaton, url, pool ) ); SC_SVN_ERR( svn_client__open_ra_session( &raSession, raLib, url, 0, 0, 0, false, false, _context->_context, pool ) ); // raLib->open? SC_SVN_ERR( raLib->get_latest_revnum( raSession, resultRev, pool ) ); return SC_SVN_SUCCESS; } Error* Client::getReposRoot( const sc::String& pathOrUrl, sc::String& root ) { apr::Pool pool( _pool ); const char* url; void* raSession; void* raBaton; svn_ra_plugin_t* raLib; const char *resultUrl; SC_SVN_ERR( svn_client_url_from_path( &url, pathOrUrl, pool ) ); SC_SVN_ERR( svn_ra_init_ra_libs( &raBaton, pool ) ); SC_SVN_ERR( svn_ra_get_ra_library( &raLib, raBaton, url, pool ) ); SC_SVN_ERR( svn_client__open_ra_session( &raSession, raLib, url, 0, 0, 0, false, false, _context->_context, pool ) ); //raLib->open? SC_SVN_ERR( raLib->get_repos_root( raSession, &resultUrl, pool ) ); root = sc::String(resultUrl); return SC_SVN_SUCCESS; } #endif const ClientContext* Client::getContext() const { return _context; } void Client::setCommitBaton( CommitBaton* baton ) { _context->_context->log_msg_baton = baton; } /** * \brief fills an svn_opt_revsion_t structure from a Revision object. * * \param rev the source Revision object. * \param svnrev the target svn_opt_revision_t structure. */ void Client::convertRevision( const Revision* rev, svn_opt_revision_t* svnrev ) { svnrev->kind = (svn_opt_revision_kind)rev->getKind(); switch( rev->getKind() ) { case Revision_Number: { const RevisionNumber* daterev = static_cast(rev); svnrev->value.number = daterev->getNumber(); break; } case Revision_Date: { const RevisionDate* daterev = static_cast(rev); svnrev->value.date = daterev->getDate(); break; } default: { break; } } } const char* Client::preparePathOrUrl( const char* pathOrUrl, apr_pool_t* pool ) { // based on svn_opt_args_to_target_array2 const char* prepared = pathOrUrl; if( svn_path_is_url(pathOrUrl) ) { // do anything necessary to create proper url prepared = svn_path_uri_from_iri( prepared, pool ); prepared = svn_path_uri_autoescape( prepared, pool ); prepared = svn_path_canonicalize( prepared, pool ); } else { prepared = svn_path_canonicalize( prepared, pool ); } return prepared; } } // namespace