blob: af86cb90b2e609b904e3a4bd20c8b7b52cf7b2ef [file] [log] [blame]
/*
* diff.c: comparing and merging
*
* ====================================================================
* Copyright (c) 2000-2002 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/.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#include <apr_strings.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include "svn_wc.h"
#include "svn_delta.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_test.h"
#include "svn_io.h"
#include "svn_pools.h"
#include "client.h"
#include <assert.h>
/*-----------------------------------------------------------------*/
/*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
struct diff_cmd_baton {
const apr_array_header_t *options;
apr_pool_t *pool;
apr_file_t *outfile;
apr_file_t *errfile;
};
/* The main workhorse, which invokes an external 'diff' program on the
two temporary files. The path is the "true" label to use in the
diff output, and the revnums are ignored. */
static svn_error_t *
diff_file_changed (const char *path,
const char *tmpfile1,
const char *tmpfile2,
svn_revnum_t rev1,
svn_revnum_t rev2,
void *diff_baton)
{
struct diff_cmd_baton *diff_cmd_baton = diff_baton;
const char **args = NULL;
int nargs, exitcode;
apr_file_t *outfile = diff_cmd_baton->outfile;
apr_file_t *errfile = diff_cmd_baton->errfile;
apr_pool_t *subpool = svn_pool_create (diff_cmd_baton->pool);
const char *label = path;
/* Execute local diff command on these two paths, print to stdout. */
nargs = diff_cmd_baton->options->nelts;
if (nargs)
{
int i;
args = apr_palloc (subpool, nargs * sizeof (char *));
for (i = 0; i < diff_cmd_baton->options->nelts; i++)
{
args[i] =
((svn_stringbuf_t **)(diff_cmd_baton->options->elts))[i]->data;
}
assert (i == nargs);
}
/* Print out the diff header. */
apr_file_printf (outfile, "Index: %s\n", label ? label : tmpfile1);
apr_file_printf (outfile,
"===================================================================\n");
SVN_ERR (svn_io_run_diff (".", args, nargs, path,
tmpfile1, tmpfile2,
&exitcode, outfile, errfile, subpool));
/* ### todo: Handle exit code == 2 (i.e. errors with diff) here */
/* ### todo: someday we'll need to worry about whether we're going
to need to write a diff plug-in mechanism that makes use of the
two paths, instead of just blindly running SVN_CLIENT_DIFF. */
/* Destroy the subpool. */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* The because the repos-diff editor passes at least one empty file to
each of these next two functions, they can be dumb wrappers around
the main workhorse routine. */
static svn_error_t *
diff_file_added (const char *path,
const char *tmpfile1,
const char *tmpfile2,
void *diff_baton)
{
return diff_file_changed (path, tmpfile1, tmpfile2,
SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
diff_baton);
}
static svn_error_t *
diff_file_deleted (const char *path,
const char *tmpfile1,
const char *tmpfile2,
void *diff_baton)
{
return diff_file_changed (path, tmpfile1, tmpfile2,
SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
diff_baton);
}
/* For now, let's have 'svn diff' send feedback to the top-level
application, so that something reasonable about directories and
propsets gets printed to stdout. */
static svn_error_t *
diff_dir_added (const char *path,
void *diff_baton)
{
/* ### todo: send feedback to app */
return SVN_NO_ERROR;
}
static svn_error_t *
diff_dir_deleted (const char *path,
void *diff_baton)
{
/* ### todo: send feedback to app */
return SVN_NO_ERROR;
}
static svn_error_t *
diff_props_changed (const char *path,
const apr_array_header_t *propchanges,
void *diff_baton)
{
/* ### todo: send feedback to app. */
return SVN_NO_ERROR;
}
/* The main callback table for 'svn diff'. */
static const svn_diff_callbacks_t
diff_callbacks =
{
diff_file_changed,
diff_file_added,
diff_file_deleted,
diff_dir_added,
diff_dir_deleted,
diff_props_changed
};
/*-----------------------------------------------------------------*/
/*** Callbacks for 'svn merge', invoked by the repos-diff editor. ***/
struct merge_cmd_baton {
apr_pool_t *pool;
};
static svn_error_t *
merge_file_changed (const char *mine,
const char *older,
const char *yours,
svn_revnum_t older_rev,
svn_revnum_t yours_rev,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create (merge_b->pool);
const char *target_label = ".working";
const char *left_label = apr_psprintf (subpool, ".r%ld", older_rev);
const char *right_label = apr_psprintf (subpool, ".r%ld", yours_rev);
svn_error_t *err;
/* This callback is essentially no more than a wrapper around
svn_wc_merge(). Thank goodness that all the
diff-editor-mechanisms are doing the hard work of getting the
fulltexts! */
err = svn_wc_merge (older, yours, mine,
left_label, right_label, target_label,
subpool);
if (err && (err->apr_err != SVN_ERR_WC_CONFLICT))
return err;
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_file_added (const char *mine,
const char *older,
const char *yours,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create (merge_b->pool);
enum svn_node_kind kind;
SVN_ERR (svn_io_check_path (mine, &kind, subpool));
switch (kind)
{
case svn_node_none:
SVN_ERR (svn_io_copy_file (yours, mine, TRUE, subpool));
SVN_ERR (svn_client_add (svn_stringbuf_create (mine, subpool),
FALSE, NULL, NULL, subpool));
break;
case svn_node_dir:
/* ### create a .drej conflict or something someday? */
return svn_error_createf (SVN_ERR_WC_NOT_FILE, 0, NULL, subpool,
"Cannot create file '%s' for addition, "
"because a directory by that name "
"already exists.", mine);
case svn_node_file:
{
/* file already exists, is it under version control? */
svn_wc_entry_t *entry;
SVN_ERR (svn_wc_entry (&entry, svn_stringbuf_create (mine, subpool),
subpool));
if (entry)
{
if (entry->schedule == svn_wc_schedule_delete)
{
/* If already scheduled for deletion, then carry out
an add, which is really a (R)eplacement. */
SVN_ERR (svn_io_copy_file (yours, mine, TRUE, subpool));
SVN_ERR (svn_client_add (svn_stringbuf_create (mine, subpool),
FALSE, NULL, NULL, subpool));
}
else
{
svn_error_t *err;
err = svn_wc_merge (older, yours, mine,
".older", ".yours", ".working", /* ###? */
subpool);
if (err && (err->apr_err != SVN_ERR_WC_CONFLICT))
return err;
}
}
else
return svn_error_createf (SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL,
subpool,
"Cannot create file '%s' for addition, "
"because an unversioned file by that name "
"already exists.", mine);
break;
}
default:
break;
}
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_file_deleted (const char *mine,
const char *older,
const char *yours,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create (merge_b->pool);
enum svn_node_kind kind;
SVN_ERR (svn_io_check_path (mine, &kind, subpool));
switch (kind)
{
case svn_node_file:
SVN_ERR (svn_client_delete (NULL,
svn_stringbuf_create (mine, subpool),
FALSE, /* don't force */
NULL, NULL, NULL, NULL, NULL, subpool));
break;
case svn_node_dir:
/* ### create a .drej conflict or something someday? */
return svn_error_createf (SVN_ERR_WC_NOT_FILE, 0, NULL, subpool,
"Cannot schedule file '%s' for deletion, "
"because a directory by that name "
"already exists.", mine);
case svn_node_none:
/* file is already non-existent, this is a no-op. */
break;
default:
break;
}
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_dir_added (const char *path,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create (merge_b->pool);
svn_stringbuf_t *path_s = svn_stringbuf_create (path, subpool);
enum svn_node_kind kind;
svn_boolean_t is_wc;
SVN_ERR (svn_io_check_path (path, &kind, subpool));
switch (kind)
{
case svn_node_none:
SVN_ERR (svn_client_mkdir (NULL, path_s, NULL, NULL, NULL,
NULL, NULL, subpool));
SVN_ERR (svn_client_add (path_s, FALSE, NULL, NULL, subpool));
break;
case svn_node_dir:
/* dir already exists. make sure it's under version control. */
SVN_ERR (svn_wc_check_wc (path_s, &is_wc, subpool));
if (! is_wc)
SVN_ERR (svn_client_add (path_s, FALSE, NULL, NULL, subpool));
break;
case svn_node_file:
/* ### create a .drej conflict or something someday? */
return svn_error_createf (SVN_ERR_WC_NOT_DIRECTORY, 0, NULL, subpool,
"Cannot create directory '%s' for addition, "
"because a file by that name "
"already exists.", path);
break;
default:
break;
}
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_dir_deleted (const char *path,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create (merge_b->pool);
enum svn_node_kind kind;
SVN_ERR (svn_io_check_path (path, &kind, subpool));
switch (kind)
{
case svn_node_dir:
SVN_ERR (svn_client_delete (NULL,
svn_stringbuf_create (path, subpool),
FALSE, /* don't force */
NULL, NULL, NULL, NULL, NULL, subpool));
break;
case svn_node_file:
/* ### create a .drej conflict or something someday? */
return svn_error_createf (SVN_ERR_WC_NOT_DIRECTORY, 0, NULL, subpool,
"Cannot schedule directory '%s' for deletion, "
"because a file by that name "
"already exists.", path);
case svn_node_none:
/* dir is already non-existent, this is a no-op. */
break;
default:
break;
}
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_props_changed (const char *path,
const apr_array_header_t *propchanges,
void *baton)
{
apr_array_header_t *entry_props, *wc_props, *regular_props;
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create (merge_b->pool);
SVN_ERR (svn_categorize_props (propchanges,
&entry_props, &wc_props, &regular_props,
subpool));
/* We only want to merge "regular" version properties: by
definition, 'svn merge' shouldn't touch any data within .svn/ */
if (regular_props)
SVN_ERR (svn_wc_merge_prop_diffs (path, regular_props, subpool));
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* The main callback table for 'svn merge'. */
static const svn_diff_callbacks_t
merge_callbacks =
{
merge_file_changed,
merge_file_added,
merge_file_deleted,
merge_dir_added,
merge_dir_deleted,
merge_props_changed
};
/*-----------------------------------------------------------------------*/
/** The logic behind 'svn diff' and 'svn merge'. */
/* Hi! This is a comment left behind by Karl, and Ben is too afraid
to erase it at this time, because he's not fully confident that all
this knowledge has been grokked yet.
There are five cases:
1. path is not an URL and start_revision != end_revision
2. path is not an URL and start_revision == end_revision
3. path is an URL and start_revision != end_revision
4. path is an URL and start_revision == end_revision
5. path is not an URL and no revisions given
With only one distinct revision the working copy provides the
other. When path is an URL there is no working copy. Thus
1: compare repository versions for URL coresponding to working copy
2: compare working copy against repository version
3: compare repository versions for URL
4: nothing to do.
5: compare working copy against text-base
Case 4 is not as stupid as it looks, for example it may occur if
the user specifies two dates that resolve to the same revision. */
/* Helper function: given a working-copy PATH, return its associated
url in *URL, allocated in POOL. If PATH is *already* a URL, that's
fine, just set *URL = PATH. */
static svn_error_t *
convert_to_url (svn_stringbuf_t **url,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_string_t path_str;
svn_boolean_t path_is_url;
path_str.data = path->data;
path_str.len = path->len;
path_is_url = svn_path_is_url (&path_str);
if (path_is_url)
*url = path;
else
{
svn_wc_entry_t *entry;
SVN_ERR (svn_wc_entry (&entry, path, pool));
if (entry)
*url = svn_stringbuf_dup (entry->url, pool);
else
return svn_error_createf
(SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"convert_to_url: %s is not versioned", path->data);
}
return SVN_NO_ERROR;
}
/* PATH1, PATH2, and TARGET_WCPATH all better be directories. For
the single file case, the caller do the merging manually. */
static svn_error_t *
do_merge (const svn_delta_editor_t *after_editor,
void *after_edit_baton,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path1,
const svn_client_revision_t *revision1,
svn_stringbuf_t *path2,
const svn_client_revision_t *revision2,
svn_stringbuf_t *target_wcpath,
svn_boolean_t recurse,
const svn_diff_callbacks_t *callbacks,
void *callback_baton,
apr_pool_t *pool)
{
svn_revnum_t start_revnum, end_revnum;
svn_stringbuf_t *URL1, *URL2;
void *ra_baton, *session, *session2;
svn_ra_plugin_t *ra_lib;
const svn_ra_reporter_t *reporter;
void *report_baton;
const svn_delta_edit_fns_t *diff_editor;
const svn_delta_editor_t *composed_editor, *new_diff_editor;
void *diff_edit_baton, *composed_edit_baton, *new_diff_edit_baton;
/* Sanity check -- ensure that we have valid revisions to look at. */
if ((revision1->kind == svn_client_revision_unspecified)
|| (revision2->kind == svn_client_revision_unspecified))
{
return svn_error_create
(SVN_ERR_CLIENT_BAD_REVISION, 0, NULL, pool,
"do_merge: caller failed to specify all revisions");
}
/* Make sure we have two URLs ready to go.*/
SVN_ERR (convert_to_url (&URL1, path1, pool));
SVN_ERR (convert_to_url (&URL2, path2, pool));
/* Establish first RA session to URL1. */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL1->data, pool));
SVN_ERR (svn_client__open_ra_session (&session, ra_lib, URL1, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
/* Resolve the revision numbers. */
SVN_ERR (svn_client__get_revision_number
(&start_revnum, ra_lib, session, revision1, path1->data, pool));
SVN_ERR (svn_client__get_revision_number
(&end_revnum, ra_lib, session, revision2, path2->data, pool));
/* Open a second session used to request individual file
contents. Although a session can be used for multiple requests, it
appears that they must be sequential. Since the first request, for
the diff, is still being processed the first session cannot be
reused. This applies to ra_dav, ra_local does not appears to have
this limitation. */
SVN_ERR (svn_client__open_ra_session (&session2, ra_lib, URL1, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
SVN_ERR (svn_client__get_diff_editor (target_wcpath,
callbacks,
callback_baton,
recurse,
ra_lib, session2,
start_revnum,
&new_diff_editor,
&new_diff_edit_baton,
pool));
if (after_editor)
{
svn_delta_compose_editors (&composed_editor, &composed_edit_baton,
new_diff_editor, new_diff_edit_baton,
after_editor, after_edit_baton, pool);
}
else
{
composed_editor = new_diff_editor;
composed_edit_baton = new_diff_edit_baton;
}
/* ### Make composed editor look "old" style. Remove someday. */
svn_delta_compat_wrap (&diff_editor, &diff_edit_baton,
composed_editor, composed_edit_baton, pool);
SVN_ERR (ra_lib->do_switch (session,
&reporter, &report_baton,
end_revnum,
NULL,
recurse,
URL2,
diff_editor, diff_edit_baton));
SVN_ERR (reporter->set_path (report_baton, "", start_revnum));
SVN_ERR (reporter->finish_report (report_baton));
SVN_ERR (ra_lib->close (session2));
SVN_ERR (ra_lib->close (session));
return SVN_NO_ERROR;
}
/* The single-file, simplified version of do_merge. */
static svn_error_t *
do_single_file_merge (const svn_delta_editor_t *after_editor,
void *after_edit_baton,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path1,
const svn_client_revision_t *revision1,
svn_stringbuf_t *path2,
const svn_client_revision_t *revision2,
svn_stringbuf_t *target_wcpath,
apr_pool_t *pool)
{
apr_status_t status;
svn_error_t *err;
apr_file_t *fp1 = NULL, *fp2 = NULL;
svn_stringbuf_t *tmpfile1, *tmpfile2, *URL1, *URL2;
svn_stream_t *fstream1, *fstream2;
const char *oldrev_str, *newrev_str;
svn_revnum_t rev1, rev2;
apr_hash_t *props1, *props2;
apr_array_header_t *propchanges;
void *ra_baton, *session1, *session2;
svn_ra_plugin_t *ra_lib;
props1 = apr_hash_make (pool);
props2 = apr_hash_make (pool);
/* Create two temporary files that contain the fulltexts of
PATH1@REV1 and PATH2@REV2. */
SVN_ERR (svn_io_open_unique_file (&fp1, &tmpfile1,
target_wcpath->data, ".tmp",
FALSE, pool));
SVN_ERR (svn_io_open_unique_file (&fp2, &tmpfile2,
target_wcpath->data, ".tmp",
FALSE, pool));
fstream1 = svn_stream_from_aprfile (fp1, pool);
fstream2 = svn_stream_from_aprfile (fp2, pool);
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (convert_to_url (&URL1, path1, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL1->data, pool));
SVN_ERR (svn_client__open_ra_session (&session1, ra_lib, URL1, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
SVN_ERR (svn_client__get_revision_number
(&rev1, ra_lib, session1, revision1, path1->data, pool));
SVN_ERR (ra_lib->get_file (session1, "", rev1, fstream1, NULL, &props1));
SVN_ERR (ra_lib->close (session1));
/* ### heh, funny. we could be fetching two fulltexts from two
*totally* different repositories here. :-) */
SVN_ERR (convert_to_url (&URL2, path2, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL2->data, pool));
SVN_ERR (svn_client__open_ra_session (&session2, ra_lib, URL2, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
SVN_ERR (svn_client__get_revision_number
(&rev2, ra_lib, session2, revision2, path2->data, pool));
SVN_ERR (ra_lib->get_file (session2, "", rev2, fstream2, NULL, &props2));
SVN_ERR (ra_lib->close (session2));
status = apr_file_close (fp1);
if (status)
return svn_error_createf (status, 0, NULL, pool, "failed to close '%s'.",
tmpfile1->data);
status = apr_file_close (fp2);
if (status)
return svn_error_createf (status, 0, NULL, pool, "failed to close '%s'.",
tmpfile2->data);
/* Perform a 3-way merge between the temporary fulltexts and the
current working file. */
oldrev_str = apr_psprintf (pool, ".r%ld", rev1);
newrev_str = apr_psprintf (pool, ".r%ld", rev2);
err = svn_wc_merge (tmpfile1->data, tmpfile2->data,
target_wcpath->data,
oldrev_str, newrev_str, ".working", pool);
if (err && (err->apr_err != SVN_ERR_WC_CONFLICT))
return err;
SVN_ERR (svn_io_remove_file (tmpfile1->data, pool));
SVN_ERR (svn_io_remove_file (tmpfile2->data, pool));
/* Deduce property diffs, and merge those too. */
SVN_ERR (svn_wc_get_local_propchanges (&propchanges,
props1, props2, pool));
SVN_ERR (svn_wc_merge_prop_diffs (target_wcpath->data,
propchanges, pool));
/* Let's be nice and give feedback via the 'after' editor, just like
the recursive-directory do_merge() routine would do, by virtue of
being driven by dir_delta(). ### someday we'll use only the
notification vtable for this, instead of a trace editor. */
if (after_editor)
{
void *root_baton, *file_baton, *handler_baton;
svn_stringbuf_t *parent, *basename;
svn_txdelta_window_handler_t handler;
svn_path_split (target_wcpath, &parent, &basename, pool);
SVN_ERR (after_editor->open_root (after_edit_baton,
SVN_INVALID_REVNUM, pool,
&root_baton));
SVN_ERR (after_editor->open_file (basename->data, root_baton,
SVN_INVALID_REVNUM, pool,
&file_baton));
SVN_ERR (after_editor->apply_textdelta (file_baton,
&handler, &handler_baton));
if (propchanges->nelts > 0)
{
apr_array_header_t *entry_props, *wc_props, *regular_props;
SVN_ERR (svn_categorize_props (propchanges,
&entry_props,
&wc_props,
&regular_props,
pool));
/* ### this is so silly; we just want the trace-update editor
to print a UU or _U here.... it ignores the prop. */
if (regular_props->nelts > 0)
SVN_ERR (after_editor->change_file_prop
(file_baton,
"foo",
svn_string_create ("foo", pool), pool));
}
SVN_ERR (after_editor->close_file (file_baton));
SVN_ERR (after_editor->close_edit (after_edit_baton));
}
return SVN_NO_ERROR;
}
/* This function handles single-files and directories both. */
static svn_error_t *
do_diff (const apr_array_header_t *options,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path1,
const svn_client_revision_t *revision1,
svn_stringbuf_t *path2,
const svn_client_revision_t *revision2,
svn_boolean_t recurse,
const svn_diff_callbacks_t *callbacks,
void *callback_baton,
apr_pool_t *pool)
{
svn_revnum_t start_revnum, end_revnum;
svn_string_t path_str;
svn_boolean_t path_is_url;
svn_stringbuf_t *anchor = NULL, *target = NULL;
svn_wc_entry_t *entry;
svn_stringbuf_t *URL = path1;
void *ra_baton, *session;
svn_ra_plugin_t *ra_lib;
const svn_ra_reporter_t *reporter;
void *report_baton;
const svn_delta_edit_fns_t *diff_editor;
void *diff_edit_baton;
/* Return an error if PATH1 and PATH2 aren't the same (for now). */
if (! svn_stringbuf_compare (path1, path2))
return svn_error_createf (SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"Multi-path diff is currently unsupprted");
/* Sanity check -- ensure that we have valid revisions to look at. */
if ((revision1->kind == svn_client_revision_unspecified)
|| (revision2->kind == svn_client_revision_unspecified))
{
return svn_error_create
(SVN_ERR_CLIENT_BAD_REVISION, 0, NULL, pool,
"do_diff: caller failed to specify any revisions");
}
/* Determine if the target we have been given is a path or an URL.
If it is a working copy path, we'll need to extract the URL from
the entry for that path. */
path_str.data = path1->data;
path_str.len = path1->len;
if (! ((path_is_url = svn_path_is_url (&path_str))))
{
SVN_ERR (svn_wc_get_actual_target (path1, &anchor, &target, pool));
SVN_ERR (svn_wc_entry (&entry, anchor, pool));
URL = svn_stringbuf_create (entry->url->data, pool);
}
/* If we are diffing a working copy path, simply use this 'quick'
diff that does not contact the repository and simply uses the
text base. */
if ((! path_is_url)
&& ((revision1->kind == svn_client_revision_committed)
|| (revision1->kind == svn_client_revision_base))
&& (revision2->kind == svn_client_revision_working))
{
return svn_wc_diff (anchor, target, callbacks, callback_baton,
recurse, pool);
}
/* Else we must contact the repository. */
/* Establish RA session */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL->data, pool));
/* ### TODO: We have to pass null for the base_dir here, since the
working copy does not match the requested revision. It might be
possible to have a special ra session for diff, where get_wc_prop
cooperates with the editor and returns values when the file is in the
wc, and null otherwise. */
SVN_ERR (svn_client__open_ra_session (&session, ra_lib, URL, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
/* ### todo: later, when we take two (or N) targets and a more
sophisticated indication of their interaction with start and end,
these calls will need to be rearranged. */
SVN_ERR (svn_client__get_revision_number
(&start_revnum, ra_lib, session, revision1, path1->data, pool));
SVN_ERR (svn_client__get_revision_number
(&end_revnum, ra_lib, session, revision2, path1->data, pool));
/* ### todo: For the moment, we'll maintain the two distinct diff
editors, svn_wc vs svn_client, in order to get this change done
in a finite amount of time. Next the two editors will be unified
into one "lazy" editor that uses local data whenever possible. */
if (revision2->kind == svn_client_revision_working)
{
/* The working copy is involved in this case. */
SVN_ERR (svn_wc_get_diff_editor (anchor, target,
callbacks, callback_baton,
recurse,
&diff_editor, &diff_edit_baton,
pool));
SVN_ERR (ra_lib->do_update (session,
&reporter, &report_baton,
start_revnum,
target,
recurse,
diff_editor, diff_edit_baton));
SVN_ERR (svn_wc_crawl_revisions (path1, reporter, report_baton,
FALSE, recurse,
NULL, NULL, /* notification is N/A */
pool));
}
else /* ### todo: there may be uncovered cases remaining */
{
/* Pure repository comparison. */
const svn_delta_editor_t *new_diff_editor;
void *new_diff_edit_baton;
/* Open a second session used to request individual file
contents. Although a session can be used for multiple requests, it
appears that they must be sequential. Since the first request, for
the diff, is still being processed the first session cannot be
reused. This applies to ra_dav, ra_local does not appears to have
this limitation. */
void *session2;
/* ### TODO: Forcing the base_dir to null. It might be possible to
use a special ra session that cooperates with the editor to enable
get_wc_prop to return values when the file is in the wc */
SVN_ERR (svn_client__open_ra_session (&session2, ra_lib, URL, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
/* Get the true diff editor. */
SVN_ERR (svn_client__get_diff_editor (target,
callbacks,
callback_baton,
recurse,
ra_lib, session2,
start_revnum,
&new_diff_editor,
&new_diff_edit_baton,
pool));
/* ### Make diff editor look "old" style. Remove someday. */
svn_delta_compat_wrap (&diff_editor, &diff_edit_baton,
new_diff_editor, new_diff_edit_baton, pool);
SVN_ERR (ra_lib->do_update (session,
&reporter, &report_baton,
end_revnum,
target,
recurse,
diff_editor, diff_edit_baton));
SVN_ERR (reporter->set_path (report_baton, "", start_revnum));
SVN_ERR (reporter->finish_report (report_baton));
SVN_ERR (ra_lib->close (session2));
}
SVN_ERR (ra_lib->close (session));
return SVN_NO_ERROR;
}
/*----------------------------------------------------------------------- */
/*** Public Interfaces. ***/
/* Display context diffs between two PATH/REVISION pairs. Each of
these input will be one of the following:
- a repository URL at a given revision.
- a working copy path, ignoring no local mods.
- a working copy path, including local mods.
We can establish a matrix that shows the nine possible types of
diffs we expect to support.
` . DST || URL:rev | WC:base | WC:working |
` . || | | |
SRC ` . || | | |
============++============+============+============+
URL:rev || (*) | (*) | (*) |
|| | | |
|| | | |
|| | | |
------------++------------+------------+------------+
WC:base || (*) | |
|| | New svn_wc_diff which |
|| | is smart enough to |
|| | handle two WC paths |
------------++------------+ and their related +
WC:working || (*) | text-bases and working |
|| | files. This operation |
|| | is entirely local. |
|| | |
------------++------------+------------+------------+
* These cases require server communication.
Svn_client_diff() is the single entry point for all of the diff
operations, and will be in charge of examining the inputs and
making decisions about how to accurately report contextual diffs.
NOTE: In the near future, svn_client_diff() will likely only
continue to report textual differences in files. Property diffs
are important, too, and will need to be supported in some fashion
so that this code can be re-used for svn_client_merge().
*/
svn_error_t *
svn_client_diff (const apr_array_header_t *options,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path1,
const svn_client_revision_t *revision1,
svn_stringbuf_t *path2,
const svn_client_revision_t *revision2,
svn_boolean_t recurse,
apr_file_t *outfile,
apr_file_t *errfile,
apr_pool_t *pool)
{
struct diff_cmd_baton diff_cmd_baton;
diff_cmd_baton.options = options;
diff_cmd_baton.pool = pool;
diff_cmd_baton.outfile = outfile;
diff_cmd_baton.errfile = errfile;
return do_diff (options,
auth_baton,
path1, revision1,
path2, revision2,
recurse,
&diff_callbacks, &diff_cmd_baton,
pool);
}
svn_error_t *
svn_client_merge (const svn_delta_editor_t *after_editor,
void *after_edit_baton,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path1,
const svn_client_revision_t *revision1,
svn_stringbuf_t *path2,
const svn_client_revision_t *revision2,
svn_stringbuf_t *target_wcpath,
svn_boolean_t recurse,
apr_pool_t *pool)
{
svn_wc_entry_t *entry;
SVN_ERR (svn_wc_entry (&entry, target_wcpath, pool));
if (entry == NULL)
return svn_error_createf (SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"Can't merge changes into '%s':"
"it's not under revision control.",
target_wcpath->data);
/* If our target_wcpath is a single file, assume that PATH1 and
PATH2 are files as well, and do a single-file merge. */
if (entry->kind == svn_node_file)
{
SVN_ERR (do_single_file_merge (after_editor, after_edit_baton,
auth_baton,
path1, revision1,
path2, revision2,
target_wcpath,
pool));
}
/* Otherwise, this must be a directory merge. Do the fancy
recursive diff-editor thing. */
else if (entry->kind == svn_node_dir)
{
struct merge_cmd_baton merge_cmd_baton;
merge_cmd_baton.pool = pool;
SVN_ERR (do_merge (after_editor, after_edit_baton,
auth_baton,
path1,
revision1,
path2,
revision2,
target_wcpath,
recurse,
&merge_callbacks,
&merge_cmd_baton,
pool));
}
return SVN_NO_ERROR;
}
/* --------------------------------------------------------------
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */