/*
* ====================================================================
* 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 <svn_client.h>
#include <svn_path.h>
#include <svn_ra.h>
#include <svn_wc.h>
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
syntax highlighted by Code2HTML, v. 0.9.1