/* * ==================================================================== * Copyright (c) 2000-2004 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */ /* ==================================================================== * 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 "config.h" #include "VisualDiff.h" #include "util/CommandArgs.h" // svn #include #include #include #include namespace svn { static svn_error_t* getRevNumber( svn_revnum_t *revnum, svn_ra_session_t* session, const svn_opt_revision_t *revision, const char *path, apr_pool_t* pool ) { // svn_client__get_revision_number /* sanity checks */ if( session == NULL && ( (revision->kind == svn_opt_revision_date) || (revision->kind == svn_opt_revision_head) ) ) { return svn_error_create( SVN_ERR_CLIENT_RA_ACCESS_REQUIRED, NULL, NULL ); } if( path == NULL ) return svn_error_create( SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED, NULL, NULL ); if( revision->kind == svn_opt_revision_number ) *revnum = revision->value.number; else if( revision->kind == svn_opt_revision_date ) SVN_ERR( svn_ra_get_dated_revision( session, revnum, revision->value.date, pool) ); else if( revision->kind == svn_opt_revision_head ) SVN_ERR( svn_ra_get_latest_revnum( session, revnum, pool ) ); else if (revision->kind == svn_opt_revision_unspecified) *revnum = SVN_INVALID_REVNUM; else if( (revision->kind == svn_opt_revision_committed) || (revision->kind == svn_opt_revision_working) || (revision->kind == svn_opt_revision_base) || (revision->kind == svn_opt_revision_previous) ) { svn_wc_adm_access_t* access; const svn_wc_entry_t* entry; SVN_ERR( svn_wc_adm_probe_open3( &access, NULL, path, FALSE, 0, NULL, NULL, pool ) ); SVN_ERR( svn_wc_entry( &entry, path, access, FALSE, pool) ); SVN_ERR( svn_wc_adm_close(access) ); if(! entry) return svn_error_createf( SVN_ERR_UNVERSIONED_RESOURCE, NULL, "'%s' is not under version control", svn_path_local_style(path, pool) ); if( (revision->kind == svn_opt_revision_base) || (revision->kind == svn_opt_revision_working) ) *revnum = entry->revision; else { *revnum = entry->cmt_rev; if( revision->kind == svn_opt_revision_previous ) (*revnum)--; } } else { return svn_error_createf( SVN_ERR_CLIENT_BAD_REVISION, NULL, "Unrecognized revision type requested for '%s'", svn_path_local_style( path, pool ) ); } return SVN_NO_ERROR; } /////////////////////////////////////////////////////////////////////////////// VisualDiff::VisualDiff( svn_client_ctx_t* context, const sc::String& diffCmd, apr_pool_t* pool ) : _pool(pool), _context(context), _diffCmd(diffCmd) { } VisualDiff::~VisualDiff() { if( ! _removePath1.isEmpty() ) { svn_error_t* svnerr = svn_io_remove_file( _removePath1, _pool ); if( svnerr != SVN_NO_ERROR ) { // todo } } if( ! _removePath2.isEmpty() ) { svn_error_t* svnerr = svn_io_remove_file( _removePath2, _pool ); if( svnerr != SVN_NO_ERROR ) { // todo } } } svn_error_t* VisualDiff::run( const char* path1, const svn_opt_revision_t* revision1, const char* path2, const svn_opt_revision_t* revision2 ) { svn_opt_revision_t peg; peg.kind = svn_opt_revision_unspecified; bool isRepos1 = svn_path_is_url(path1) == TRUE; bool isRepos2 = svn_path_is_url(path2) == TRUE; if( (revision1->kind == svn_opt_revision_unspecified) || (revision2->kind == svn_opt_revision_unspecified) ) return svn_error_create( SVN_ERR_CLIENT_BAD_REVISION, NULL, "Not all required revisions are specified"); bool isLocal1 = ((revision1->kind == svn_opt_revision_base) || (revision1->kind == svn_opt_revision_working)); bool isLocal2 = ((revision2->kind == svn_opt_revision_base) || (revision2->kind == svn_opt_revision_working)); if ((! isRepos1) && (! isLocal1)) isRepos1 = true; if ((! isRepos2) && (! isLocal2)) isRepos2 = true; if( isRepos1 ) { if( isRepos2 ) { // repository <-> repository return diffRpRp( path1, revision1, path2, revision2, &peg ); } else { // repository <-> working return diffRpWc( path1, revision1, path2, revision2, &peg, true ); } } else { if( isRepos2 ) { // working <-> repository return diffRpWc( path2, revision2, path1, revision1, &peg, false ); } else { // working <-> working return diffWcWc( path1, revision1, path2, revision2 ); } } //return SVN_NO_ERROR; } svn_error_t* VisualDiff::run( const char* path, const svn_opt_revision_t* revision1, const svn_opt_revision_t* revision2, const svn_opt_revision_t* peg ) { if( (revision1->kind == svn_opt_revision_unspecified) || (revision2->kind == svn_opt_revision_unspecified) ) return svn_error_create( SVN_ERR_CLIENT_BAD_REVISION, NULL, "Not all required revisions are specified" ); bool isLocal1 = ((revision1->kind == svn_opt_revision_base) || (revision1->kind == svn_opt_revision_working)); bool isLocal2 = ((revision2->kind == svn_opt_revision_base) || (revision2->kind == svn_opt_revision_working)); if( isLocal1 && isLocal2 ) return svn_error_create( SVN_ERR_CLIENT_BAD_REVISION, NULL, "At least one revision must be non-local for a pegged diff" ); if( ! isLocal1 ) { if( ! isLocal2 ) { // repository <-> repository return diffRpRp( path, revision1, path, revision2, peg ); } else { // repository <-> working return diffRpWc( path, revision1, path, revision2, peg, true ); } } else { if( ! isLocal2 ) { // working <-> repository diffRpWc( path, revision2, path, revision1, peg, false ); } else { // working <-> working return diffWcWc( path, revision1, path, revision2 ); } } return SVN_NO_ERROR; } svn_error_t* VisualDiff::diffRpWc( const char* path1, const svn_opt_revision_t* revision1, const char* path2, const svn_opt_revision_t* revision2, const svn_opt_revision_t* peg, bool rpwc ) { const char* url1; SVN_ERR( svn_client_url_from_path(&url1, path1, _pool) ); svn_ra_callbacks_t* cb = (svn_ra_callbacks_t*)apr_pcalloc( _pool, sizeof(*cb) ); cb->auth_baton = _context->auth_baton; svn_ra_session_t* session; SVN_ERR( svn_ra_open( &session, url1, cb, NULL, _context->config, _pool ) ); const char* reposroot; SVN_ERR( svn_ra_get_repos_root( session, &reposroot, _pool ) ); svn_revnum_t revnum1; SVN_ERR( getRevNumber( &revnum1, session, revision1, url1, _pool ) ); const char* fetchurl1 = url1; if( peg->kind != svn_opt_revision_unspecified ) { svn_revnum_t pegnum; SVN_ERR( getRevNumber( &pegnum, session, peg, url1, _pool ) ); apr_hash_t* locations; apr_array_header_t* revisions = apr_array_make( _pool, 1, sizeof(svn_revnum_t) ); APR_ARRAY_PUSH( revisions, svn_revnum_t ) = revnum1; SVN_ERR( svn_ra_get_locations( session, &locations, "", pegnum, revisions, _pool) ); const char* rurl1; rurl1 = (char*)apr_hash_get (locations, &revnum1, sizeof (svn_revnum_t)); fetchurl1 = apr_pstrcat( _pool, reposroot, rurl1, NULL ); } const char* tdir; SVN_ERR( svn_io_temp_dir( &tdir, _pool ) ); tdir = apr_pstrcat(_pool, tdir, "/subcommander", NULL ); const char* name1; apr_file_t* file1; SVN_ERR( svn_io_open_unique_file( &file1, &name1, tdir, ".tmp", false, _pool) ); _removePath1 = name1; svn_stream_t* stream1; stream1 = svn_stream_from_aprfile( file1, _pool ); svn_ra_session_t* session1; svn_revnum_t fetchnum1; SVN_ERR( svn_ra_open( &session1, fetchurl1, cb, NULL, _context->config, _pool ) ); SVN_ERR( svn_ra_get_file( session1, "", revnum1, stream1, &fetchnum1, NULL, _pool) ); apr_file_close(file1); const char* wcpath; if( revision2->kind == svn_opt_revision_base ) { // get base file SVN_ERR( svn_wc_get_pristine_copy_path( path2, &wcpath, _pool ) ); } else //(revision2->kind == svn_opt_revision_working) { // get "clean" wc file svn_wc_adm_access_t* access; SVN_ERR( svn_wc_adm_probe_open3( &access, 0, path2, false, 0, NULL, NULL, _pool ) ); SVN_ERR( svn_wc_translated_file( &wcpath, path2, access, false, _pool ) ); // if the translation created a temporary file we have to remember it // so we can remove it when it is no longer needed. sc::String sBase(path2); sc::String sWc(wcpath); if( sWc != sBase ) { _removePath2 = sWc; } } sc::String label1(fetchurl1); const char* strrev1 = apr_off_t_toa(_pool, revnum1); label1 += "\t(Revision "; label1 += strrev1; label1 += ")"; sc::String labelwc(path2); if( revision2->kind == svn_opt_revision_base ) { labelwc += "\t(Base)"; } else { labelwc += "\t(Working Copy)"; } if( rpwc ) { SVN_ERR( run( name1, label1, wcpath, labelwc ) ); } else { SVN_ERR( run( wcpath, labelwc, name1, label1 ) ); } return SVN_NO_ERROR; } svn_error_t* VisualDiff::diffRpRp( const char* path1, const svn_opt_revision_t* revision1, const char* path2, const svn_opt_revision_t* revision2, const svn_opt_revision_t* peg ) { const char* url1; const char* url2; SVN_ERR( svn_client_url_from_path(&url1, path1, _pool) ); SVN_ERR( svn_client_url_from_path(&url2, path2, _pool) ); svn_ra_callbacks_t* cb = (svn_ra_callbacks_t*)apr_pcalloc( _pool, sizeof(*cb) ); cb->auth_baton = _context->auth_baton; svn_ra_session_t* session; SVN_ERR( svn_ra_open( &session, url2, cb, NULL, _context->config, _pool ) ); const char* reposroot; SVN_ERR( svn_ra_get_repos_root( session, &reposroot, _pool ) ); svn_revnum_t revnum1; svn_revnum_t revnum2; SVN_ERR( getRevNumber( &revnum1, session, revision1, url2, _pool ) ); SVN_ERR( getRevNumber( &revnum2, session, revision2, url2, _pool ) ); const char* fetchurl1 = url1; const char* fetchurl2 = url2; if( peg->kind != svn_opt_revision_unspecified ) { svn_revnum_t pegnum; SVN_ERR( getRevNumber( &pegnum, session, peg, url2, _pool ) ); apr_hash_t* locations; apr_array_header_t* revisions = apr_array_make( _pool, 2, sizeof(svn_revnum_t) ); APR_ARRAY_PUSH( revisions, svn_revnum_t ) = revnum1; APR_ARRAY_PUSH( revisions, svn_revnum_t ) = revnum2; SVN_ERR( svn_ra_get_locations( session, &locations, "", pegnum, revisions, _pool) ); const char* rurl1; const char* rurl2; rurl1 = (char*)apr_hash_get (locations, &revnum1, sizeof (svn_revnum_t)); rurl2 = (char*)apr_hash_get (locations, &revnum2, sizeof (svn_revnum_t)); fetchurl1 = apr_pstrcat( _pool, reposroot, rurl1, NULL ); fetchurl2 = apr_pstrcat( _pool, reposroot, rurl2, NULL ); } const char* tdir; SVN_ERR( svn_io_temp_dir( &tdir, _pool ) ); tdir = apr_pstrcat(_pool, tdir, "/subcommander", NULL ); const char* name1; apr_file_t* file1; SVN_ERR( svn_io_open_unique_file( &file1, &name1, tdir, ".tmp", false, _pool) ); _removePath1 = name1; svn_stream_t* stream1; stream1 = svn_stream_from_aprfile( file1, _pool ); svn_ra_session_t* session1; svn_revnum_t fetchnum1; SVN_ERR( svn_ra_open( &session1, fetchurl1, cb, NULL, _context->config, _pool ) ); svn_error_t* err1 = svn_ra_get_file( session1, "", revnum1, stream1, &fetchnum1, NULL, _pool); apr_file_close(file1); if( err1 && err1->apr_err != SVN_ERR_FS_NOT_FOUND ) { return err1; } if( err1 && err1->apr_err == SVN_ERR_FS_NOT_FOUND ) { fetchurl1 = "file does not exist in this revision!"; } const char* name2; apr_file_t* file2; SVN_ERR( svn_io_open_unique_file( &file2, &name2, tdir, ".tmp", false, _pool) ); _removePath2 = name2; svn_stream_t* stream2; stream2 = svn_stream_from_aprfile( file2, _pool ); svn_ra_session_t* session2; svn_revnum_t fetchnum2; SVN_ERR( svn_ra_open( &session2, fetchurl2, cb, NULL, _context->config, _pool ) ); svn_error_t* err2 = svn_ra_get_file( session2, "", revnum2, stream2, &fetchnum2, NULL, _pool); apr_file_close(file2); if( err2 && err2->apr_err != SVN_ERR_FS_NOT_FOUND ) { return err2; } if( err2 && err2->apr_err == SVN_ERR_FS_NOT_FOUND ) { fetchurl2 = "file does not exist in this revision!"; } sc::String label1(fetchurl1); const char* strrev1 = apr_off_t_toa(_pool, revnum1); label1 += "\t(Revision "; label1 += strrev1; label1 += ")"; sc::String label2(fetchurl2); const char* strrev2 = apr_off_t_toa(_pool, revnum2); label2 += "\t(Revision "; label2 += strrev2; label2 += ")"; SVN_ERR( run( name1, label1, name2, label2 ) ); return SVN_NO_ERROR; } // assumes path1 == path2 with revisions base <-> working svn_error_t* VisualDiff::diffWcWc( const char* path1, const svn_opt_revision_t* revision1, const char* path2, const svn_opt_revision_t* revision2 ) { if( (strcmp(path1, path2) != 0) || (! (revision1->kind == svn_opt_revision_base) && (revision2->kind == svn_opt_revision_working)) ) return svn_error_create( SVN_ERR_INCORRECT_PARAMS, NULL, "Only diffs between a path's text-base and its working files are supported at this time"); // get base file const char* base; SVN_ERR( svn_wc_get_pristine_copy_path( path1, &base, _pool ) ); // create empty file if there is no base file apr_finfo_t info = {}; apr_status_t status = apr_stat( &info, base, APR_FINFO_SIZE, _pool ); if( status != APR_SUCCESS ) { const char* tdir; SVN_ERR( svn_io_temp_dir( &tdir, _pool ) ); tdir = apr_pstrcat(_pool, tdir, "/subcommander", NULL ); apr_file_t* file; SVN_ERR( svn_io_open_unique_file( &file, &base, tdir, ".tmp", false, _pool) ); _removePath1 = base; apr_file_close(file); } // get "clean" wc file svn_wc_adm_access_t* access; SVN_ERR( svn_wc_adm_probe_open3( &access, 0, path1, false, 0, NULL, NULL, _pool ) ); const char* wc; SVN_ERR( svn_wc_translated_file( &wc, path1, access, false, _pool ) ); // if the translation created a temporary file we have to remember it // so we can remove it when it is no longer needed. sc::String sBase(path1); sc::String sWc(wc); if( sWc != sBase ) { _removePath2 = sWc; } sc::String labelBase(path1); labelBase += "\t(Base)"; sc::String labelWc(path1); labelWc += "\t(Working Copy)"; SVN_ERR( run( base, labelBase, wc, labelWc ) ); return SVN_NO_ERROR; } svn_error_t* VisualDiff::run( const char* path1, const char* label1, const char* path2, const char* label2 ) { int result; apr_exit_why_e why; CommandArgs cmdArgs( _diffCmd, _pool ); cmdArgs.setArg( sc::String("{left}"), sc::String(path1) ); cmdArgs.setArg( sc::String("{llabel}"), sc::String(label1) ); cmdArgs.setArg( sc::String("{right}"), sc::String(path2) ); cmdArgs.setArg( sc::String("{rlabel}"), sc::String(label2) ); return svn_io_run_cmd( cmdArgs.getPath(), cmdArgs.getArgs()[0], cmdArgs.getArgs(), &result, &why, true, 0, 0, 0, _pool ); } } // namespace