#include <apr_strings.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include "svn_wc.h"
#include "svn_delta.h"
#include "svn_diff.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_io.h"
#include "svn_utf.h"
#include "svn_pools.h"
#include "svn_config.h"
#include "svn_props.h"
#include "svn_time.h"
#include "client.h"
#include <assert.h>
#include "svn_private_config.h"
static const char equal_string[] =
"===================================================================";
static const char under_string[] =
"___________________________________________________________________";
static svn_error_t *
file_printf_from_utf8(apr_file_t *fptr, const char *encoding,
const char *format, ...)
__attribute__ ((format(printf, 3, 4)));
static svn_error_t *
file_printf_from_utf8(apr_file_t *fptr, const char *encoding,
const char *format, ...)
{
va_list ap;
const char *buf, *buf_apr;
va_start(ap, format);
buf = apr_pvsprintf(apr_file_pool_get(fptr), format, ap);
va_end(ap);
SVN_ERR(svn_utf_cstring_from_utf8_ex2(&buf_apr, buf, encoding,
apr_file_pool_get(fptr)));
return svn_io_file_write_full(fptr, buf_apr, strlen(buf_apr),
NULL, apr_file_pool_get(fptr));
}
static svn_error_t *
display_prop_diffs(const apr_array_header_t *propchanges,
apr_hash_t *original_props,
const char *path,
const char *encoding,
apr_file_t *file,
apr_pool_t *pool)
{
int i;
SVN_ERR(file_printf_from_utf8(file, encoding,
_("%sProperty changes on: %s%s"),
APR_EOL_STR,
svn_path_local_style(path, pool),
APR_EOL_STR));
SVN_ERR(file_printf_from_utf8(file, encoding, "%s" APR_EOL_STR,
under_string));
for (i = 0; i < propchanges->nelts; i++)
{
const svn_prop_t *propchange
= &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
const svn_string_t *original_value;
if (original_props)
original_value = apr_hash_get(original_props,
propchange->name, APR_HASH_KEY_STRING);
else
original_value = NULL;
if ((! (original_value || propchange->value))
|| (original_value && propchange->value
&& svn_string_compare(original_value, propchange->value)))
continue;
SVN_ERR(file_printf_from_utf8(file, encoding, _("Name: %s%s"),
propchange->name, APR_EOL_STR));
{
svn_boolean_t val_is_utf8 = svn_prop_is_svn_prop(propchange->name);
if (original_value != NULL)
{
if (val_is_utf8)
{
SVN_ERR(file_printf_from_utf8
(file, encoding,
" - %s" APR_EOL_STR, original_value->data));
}
else
{
apr_file_printf
(file, " - %s" APR_EOL_STR, original_value->data);
}
}
if (propchange->value != NULL)
{
if (val_is_utf8)
{
SVN_ERR(file_printf_from_utf8
(file, encoding, " + %s" APR_EOL_STR,
propchange->value->data));
}
else
{
apr_file_printf(file, " + %s" APR_EOL_STR,
propchange->value->data);
}
}
}
}
apr_file_printf(file, APR_EOL_STR);
return SVN_NO_ERROR;
}
static svn_error_t *
check_scheme_match(svn_wc_adm_access_t *adm_access, const char *url)
{
const char *path = svn_wc_adm_access_path(adm_access);
apr_pool_t *pool = svn_wc_adm_access_pool(adm_access);
const svn_wc_entry_t *ent;
const char *idx1, *idx2;
SVN_ERR(svn_wc_entry(&ent, path, adm_access, TRUE, pool));
idx1 = strchr(url, ':');
idx2 = strchr(ent->url, ':');
if ((idx1 == NULL) && (idx2 == NULL))
{
return svn_error_createf
(SVN_ERR_BAD_URL, NULL,
_("URLs have no scheme ('%s' and '%s')"), url, ent->url);
}
else if (idx1 == NULL)
{
return svn_error_createf
(SVN_ERR_BAD_URL, NULL,
_("URL has no scheme: '%s'"), url);
}
else if (idx2 == NULL)
{
return svn_error_createf
(SVN_ERR_BAD_URL, NULL,
_("URL has no scheme: '%s'"), ent->url);
}
else if (((idx1 - url) != (idx2 - ent->url))
|| (strncmp(url, ent->url, idx1 - url) != 0))
{
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Access scheme mixtures not yet supported ('%s' and '%s')"),
url, ent->url);
}
return SVN_NO_ERROR;
}
struct diff_cmd_baton {
const apr_array_header_t *options;
apr_pool_t *pool;
apr_file_t *outfile;
apr_file_t *errfile;
const char *header_encoding;
const char *orig_path_1;
const char *orig_path_2;
svn_revnum_t revnum1;
svn_revnum_t revnum2;
apr_hash_t *config;
svn_boolean_t force_binary;
svn_boolean_t force_empty;
};
static const char *
diff_label(const char *path,
svn_revnum_t revnum,
apr_pool_t *pool)
{
const char *label;
if (revnum != SVN_INVALID_REVNUM)
label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
else
label = apr_psprintf(pool, _("%s\t(working copy)"), path);
return label;
}
static svn_error_t *
diff_props_changed(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
const apr_array_header_t *propchanges,
apr_hash_t *original_props,
void *diff_baton)
{
struct diff_cmd_baton *diff_cmd_baton = diff_baton;
apr_array_header_t *props;
apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
if (props->nelts > 0)
SVN_ERR(display_prop_diffs(props, original_props, path,
diff_cmd_baton->header_encoding,
diff_cmd_baton->outfile, subpool));
if (state)
*state = svn_wc_notify_state_unknown;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
diff_content_changed(const char *path,
const char *tmpfile1,
const char *tmpfile2,
svn_revnum_t rev1,
svn_revnum_t rev2,
const char *mimetype1,
const char *mimetype2,
void *diff_baton)
{
struct diff_cmd_baton *diff_cmd_baton = diff_baton;
const char *diff_cmd = NULL;
const char **args = NULL;
int nargs, exitcode;
apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
svn_stream_t *os;
apr_file_t *errfile = diff_cmd_baton->errfile;
const char *label1, *label2;
svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
const char *path1, *path2;
int i;
os = svn_stream_from_aprfile(diff_cmd_baton->outfile, subpool);
nargs = diff_cmd_baton->options->nelts;
if (nargs)
{
args = apr_palloc(subpool, nargs * sizeof(char *));
for (i = 0; i < diff_cmd_baton->options->nelts; i++)
{
args[i] =
((const char **)(diff_cmd_baton->options->elts))[i];
}
assert(i == nargs);
}
path1 = diff_cmd_baton->orig_path_1;
path2 = diff_cmd_baton->orig_path_2;
for (i = 0; path1[i] && path2[i] && (path1[i] == path2[i]); i++)
;
if (path1[i] || path2[i])
{
for ( ; (i > 0) && (path1[i] != '/'); i--)
;
}
path1 = path1 + i;
path2 = path2 + i;
if (path1[0] == '\0')
path1 = apr_psprintf(subpool, "%s", path);
else if (path1[0] == '/')
path1 = apr_psprintf(subpool, "%s\t(...%s)", path, path1);
else
path1 = apr_psprintf(subpool, "%s\t(.../%s)", path, path1);
if (path2[0] == '\0')
path2 = apr_psprintf(subpool, "%s", path);
else if (path2[0] == '/')
path2 = apr_psprintf(subpool, "%s\t(...%s)", path, path2);
else
path2 = apr_psprintf(subpool, "%s\t(.../%s)", path, path2);
label1 = diff_label(path1, rev1, subpool);
label2 = diff_label(path2, rev2, subpool);
if (mimetype1)
mt1_binary = svn_mime_type_is_binary(mimetype1);
if (mimetype2)
mt2_binary = svn_mime_type_is_binary(mimetype2);
if (! diff_cmd_baton->force_binary && (mt1_binary || mt2_binary))
{
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
_("Cannot display: file marked as a binary type.%s"),
APR_EOL_STR));
if (mt1_binary && !mt2_binary)
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"svn:mime-type = %s" APR_EOL_STR, mimetype1));
else if (mt2_binary && !mt1_binary)
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"svn:mime-type = %s" APR_EOL_STR, mimetype2));
else if (mt1_binary && mt2_binary)
{
if (strcmp(mimetype1, mimetype2) == 0)
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"svn:mime-type = %s" APR_EOL_STR,
mimetype1));
else
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"svn:mime-type = (%s, %s)" APR_EOL_STR,
mimetype1, mimetype2));
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
if (diff_cmd_baton->config)
{
svn_config_t *cfg = apr_hash_get(diff_cmd_baton->config,
SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING);
svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
SVN_CONFIG_OPTION_DIFF_CMD, NULL);
}
if (diff_cmd)
{
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
SVN_ERR(svn_stream_close(os));
SVN_ERR(svn_io_run_diff(".", args, nargs, label1, label2,
tmpfile1, tmpfile2,
&exitcode, diff_cmd_baton->outfile, errfile,
diff_cmd, subpool));
}
else
{
svn_diff_t *diff;
svn_diff_file_options_t *opts = svn_diff_file_options_create(subpool);
if (diff_cmd_baton->options)
SVN_ERR(svn_diff_file_options_parse(opts, diff_cmd_baton->options,
subpool));
SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2, opts,
subpool));
if (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
{
SVN_ERR(svn_stream_printf_from_utf8
(os, diff_cmd_baton->header_encoding, subpool,
"Index: %s" APR_EOL_STR "%s" APR_EOL_STR,
path, equal_string));
SVN_ERR(svn_diff_file_output_unified2
(os, diff, tmpfile1, tmpfile2, label1, label2,
diff_cmd_baton->header_encoding, subpool));
}
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
diff_file_changed(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *content_state,
svn_wc_notify_state_t *prop_state,
const char *path,
const char *tmpfile1,
const char *tmpfile2,
svn_revnum_t rev1,
svn_revnum_t rev2,
const char *mimetype1,
const char *mimetype2,
const apr_array_header_t *prop_changes,
apr_hash_t *original_props,
void *diff_baton)
{
if (tmpfile1)
SVN_ERR(diff_content_changed(path,
tmpfile1, tmpfile2, rev1, rev2,
mimetype1, mimetype2, diff_baton));
if (prop_changes->nelts > 0)
SVN_ERR(diff_props_changed(adm_access, prop_state, path, prop_changes,
original_props, diff_baton));
if (content_state)
*content_state = svn_wc_notify_state_unknown;
if (prop_state)
*prop_state = svn_wc_notify_state_unknown;
return SVN_NO_ERROR;
}
static svn_error_t *
diff_file_added(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *content_state,
svn_wc_notify_state_t *prop_state,
const char *path,
const char *tmpfile1,
const char *tmpfile2,
svn_revnum_t rev1,
svn_revnum_t rev2,
const char *mimetype1,
const char *mimetype2,
const apr_array_header_t *prop_changes,
apr_hash_t *original_props,
void *diff_baton)
{
struct diff_cmd_baton *diff_cmd_baton = diff_baton;
diff_cmd_baton->force_empty = TRUE;
SVN_ERR(diff_file_changed(adm_access, content_state, prop_state, path,
tmpfile1, tmpfile2,
rev1, rev2,
mimetype1, mimetype2,
prop_changes, original_props, diff_baton));
diff_cmd_baton->force_empty = FALSE;
return SVN_NO_ERROR;
}
static svn_error_t *
diff_file_deleted_with_diff(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
const char *tmpfile1,
const char *tmpfile2,
const char *mimetype1,
const char *mimetype2,
apr_hash_t *original_props,
void *diff_baton)
{
struct diff_cmd_baton *diff_cmd_baton = diff_baton;
return diff_file_changed(adm_access, state, NULL, path,
tmpfile1, tmpfile2,
diff_cmd_baton->revnum1, diff_cmd_baton->revnum2,
mimetype1, mimetype2,
apr_array_make(diff_cmd_baton->pool, 1,
sizeof(svn_prop_t)),
apr_hash_make(diff_cmd_baton->pool), diff_baton);
}
static svn_error_t *
diff_file_deleted_no_diff(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
const char *tmpfile1,
const char *tmpfile2,
const char *mimetype1,
const char *mimetype2,
apr_hash_t *original_props,
void *diff_baton)
{
struct diff_cmd_baton *diff_cmd_baton = diff_baton;
if (state)
*state = svn_wc_notify_state_unknown;
SVN_ERR(file_printf_from_utf8
(diff_cmd_baton->outfile,
diff_cmd_baton->header_encoding,
"Index: %s (deleted)" APR_EOL_STR "%s" APR_EOL_STR,
path, equal_string));
return SVN_NO_ERROR;
}
static svn_error_t *
diff_dir_added(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
svn_revnum_t rev,
void *diff_baton)
{
if (state)
*state = svn_wc_notify_state_unknown;
return SVN_NO_ERROR;
}
static svn_error_t *
diff_dir_deleted(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
void *diff_baton)
{
if (state)
*state = svn_wc_notify_state_unknown;
return SVN_NO_ERROR;
}
struct merge_cmd_baton {
svn_boolean_t force;
svn_boolean_t dry_run;
const char *added_path;
const char *target;
const char *url;
const char *path;
const svn_opt_revision_t *revision;
svn_client_ctx_t *ctx;
svn_boolean_t add_necessitated_merge;
apr_hash_t *dry_run_deletions;
const char *diff3_cmd;
const apr_array_header_t *merge_options;
apr_pool_t *pool;
};
apr_hash_t *
svn_client__dry_run_deletions(void *merge_cmd_baton)
{
struct merge_cmd_baton *merge_b = merge_cmd_baton;
return merge_b->dry_run_deletions;
}
static APR_INLINE svn_boolean_t
dry_run_deleted_p(struct merge_cmd_baton *merge_b, const char *wcpath)
{
return (merge_b->dry_run &&
apr_hash_get(merge_b->dry_run_deletions, wcpath,
APR_HASH_KEY_STRING) != NULL);
}
static svn_error_t *
merge_props_changed(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
const apr_array_header_t *propchanges,
apr_hash_t *original_props,
void *baton)
{
apr_array_header_t *props;
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create(merge_b->pool);
svn_error_t *err;
SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
if (props->nelts)
{
err = svn_wc_merge_props(state, path, adm_access, original_props, props,
FALSE, merge_b->dry_run, subpool);
if (err && (err->apr_err == SVN_ERR_ENTRY_NOT_FOUND
|| err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE))
{
if (state)
*state = svn_wc_notify_state_missing;
svn_error_clear(err);
return SVN_NO_ERROR;
}
else if (err)
return err;
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_file_changed(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *content_state,
svn_wc_notify_state_t *prop_state,
const char *mine,
const char *older,
const char *yours,
svn_revnum_t older_rev,
svn_revnum_t yours_rev,
const char *mimetype1,
const char *mimetype2,
const apr_array_header_t *prop_changes,
apr_hash_t *original_props,
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,
_(".merge-left.r%ld"),
older_rev);
const char *right_label = apr_psprintf(subpool,
_(".merge-right.r%ld"),
yours_rev);
svn_boolean_t has_local_mods;
svn_boolean_t merge_required = TRUE;
enum svn_wc_merge_outcome_t merge_outcome;
if (adm_access == NULL)
{
if (content_state)
*content_state = svn_wc_notify_state_missing;
if (prop_state)
*prop_state = svn_wc_notify_state_missing;
return SVN_NO_ERROR;
}
{
const svn_wc_entry_t *entry;
svn_node_kind_t kind;
SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
SVN_ERR(svn_io_check_path(mine, &kind, subpool));
if ((! entry) || (kind != svn_node_file))
{
if (content_state)
*content_state = svn_wc_notify_state_missing;
if (prop_state)
*prop_state = svn_wc_notify_state_missing;
return SVN_NO_ERROR;
}
}
if (prop_changes->nelts > 0)
SVN_ERR(merge_props_changed(adm_access, prop_state, mine, prop_changes,
original_props, baton));
else
if (prop_state)
*prop_state = svn_wc_notify_state_unchanged;
if (older)
{
SVN_ERR(svn_wc_text_modified_p(&has_local_mods, mine, FALSE,
adm_access, subpool));
if ((! has_local_mods)
&& ((mimetype1 && svn_mime_type_is_binary(mimetype1))
|| (mimetype2 && svn_mime_type_is_binary(mimetype2))))
{
svn_boolean_t older_revision_exists =
!merge_b->add_necessitated_merge;
svn_boolean_t same_contents;
SVN_ERR(svn_io_files_contents_same_p(&same_contents,
(older_revision_exists ?
older : yours),
mine, subpool));
if (same_contents)
{
if (older_revision_exists && !merge_b->dry_run)
SVN_ERR(svn_io_file_rename(yours, mine, subpool));
merge_outcome = svn_wc_merge_merged;
merge_required = FALSE;
}
}
if (merge_required)
{
SVN_ERR(svn_wc_merge2(&merge_outcome,
older, yours, mine, adm_access,
left_label, right_label, target_label,
merge_b->dry_run, merge_b->diff3_cmd,
merge_b->merge_options, subpool));
}
if (content_state)
{
if (merge_outcome == svn_wc_merge_conflict)
*content_state = svn_wc_notify_state_conflicted;
else if (has_local_mods
&& merge_outcome != svn_wc_merge_unchanged)
*content_state = svn_wc_notify_state_merged;
else if (merge_outcome == svn_wc_merge_merged)
*content_state = svn_wc_notify_state_changed;
else if (merge_outcome == svn_wc_merge_no_merge)
*content_state = svn_wc_notify_state_missing;
else
*content_state = svn_wc_notify_state_unchanged;
}
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_file_added(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *content_state,
svn_wc_notify_state_t *prop_state,
const char *mine,
const char *older,
const char *yours,
svn_revnum_t rev1,
svn_revnum_t rev2,
const char *mimetype1,
const char *mimetype2,
const apr_array_header_t *prop_changes,
apr_hash_t *original_props,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create(merge_b->pool);
svn_node_kind_t kind;
const char *copyfrom_url;
const char *child;
int i;
apr_hash_t *new_props;
if (prop_state)
*prop_state = svn_wc_notify_state_unknown;
new_props = apr_hash_copy(subpool, original_props);
for (i = 0; i < prop_changes->nelts; ++i)
{
const svn_prop_t *prop = &APR_ARRAY_IDX(prop_changes, i, svn_prop_t);
apr_hash_set(new_props, prop->name, APR_HASH_KEY_STRING, prop->value);
}
if (! adm_access)
{
if (merge_b->dry_run && merge_b->added_path
&& svn_path_is_child(merge_b->added_path, mine, subpool))
{
if (content_state)
*content_state = svn_wc_notify_state_changed;
if (prop_state && apr_hash_count(new_props))
*prop_state = svn_wc_notify_state_changed;
}
else
*content_state = svn_wc_notify_state_missing;
return SVN_NO_ERROR;
}
SVN_ERR(svn_io_check_path(mine, &kind, subpool));
switch (kind)
{
case svn_node_none:
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
if (entry && entry->schedule != svn_wc_schedule_delete)
{
if (content_state)
*content_state = svn_wc_notify_state_obstructed;
return SVN_NO_ERROR;
}
if (! merge_b->dry_run)
{
child = svn_path_is_child(merge_b->target, mine, subpool);
assert(child != NULL);
copyfrom_url = svn_path_url_add_component(merge_b->url, child,
subpool);
SVN_ERR(check_scheme_match(adm_access, copyfrom_url));
SVN_ERR(svn_wc_add_repos_file2(mine, adm_access,
yours, NULL,
new_props, NULL,
copyfrom_url,
rev2,
subpool));
}
if (content_state)
*content_state = svn_wc_notify_state_changed;
if (prop_state && apr_hash_count(new_props))
*prop_state = svn_wc_notify_state_changed;
}
break;
case svn_node_dir:
if (content_state)
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
if (entry && dry_run_deleted_p(merge_b, mine))
*content_state = svn_wc_notify_state_changed;
else
*content_state = svn_wc_notify_state_obstructed;
}
break;
case svn_node_file:
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
if (!entry || entry->schedule == svn_wc_schedule_delete)
{
if (content_state)
*content_state = svn_wc_notify_state_obstructed;
}
else
{
if (dry_run_deleted_p(merge_b, mine))
{
if (content_state)
*content_state = svn_wc_notify_state_changed;
}
else
{
merge_b->add_necessitated_merge = TRUE;
SVN_ERR(merge_file_changed(adm_access, content_state,
prop_state, mine, older, yours,
rev1, rev2,
mimetype1, mimetype2,
prop_changes, original_props,
baton));
merge_b->add_necessitated_merge = FALSE;
}
}
break;
}
default:
if (content_state)
*content_state = svn_wc_notify_state_unknown;
break;
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_file_deleted(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *mine,
const char *older,
const char *yours,
const char *mimetype1,
const char *mimetype2,
apr_hash_t *original_props,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create(merge_b->pool);
svn_node_kind_t kind;
svn_wc_adm_access_t *parent_access;
const char *parent_path;
svn_error_t *err;
if (! adm_access)
{
if (state)
*state = svn_wc_notify_state_missing;
return SVN_NO_ERROR;
}
SVN_ERR(svn_io_check_path(mine, &kind, subpool));
switch (kind)
{
case svn_node_file:
svn_path_split(mine, &parent_path, NULL, subpool);
SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent_path,
subpool));
{
err = svn_client__wc_delete(mine, parent_access, merge_b->force,
merge_b->dry_run,
NULL, NULL,
merge_b->ctx, subpool);
}
if (err && state)
{
*state = svn_wc_notify_state_obstructed;
svn_error_clear(err);
}
else if (state)
{
*state = svn_wc_notify_state_changed;
}
break;
case svn_node_dir:
if (state)
*state = svn_wc_notify_state_obstructed;
break;
case svn_node_none:
if (state)
*state = svn_wc_notify_state_missing;
break;
default:
if (state)
*state = svn_wc_notify_state_unknown;
break;
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
merge_dir_added(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
svn_revnum_t rev,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create(merge_b->pool);
svn_node_kind_t kind;
const svn_wc_entry_t *entry;
const char *copyfrom_url, *child;
if (! adm_access)
{
if (state)
{
if (merge_b->dry_run && merge_b->added_path
&& svn_path_is_child(merge_b->added_path, path, subpool))
*state = svn_wc_notify_state_changed;
else
*state = svn_wc_notify_state_missing;
}
return SVN_NO_ERROR;
}
child = svn_path_is_child(merge_b->target, path, subpool);
assert(child != NULL);
copyfrom_url = svn_path_url_add_component(merge_b->url, child, subpool);
SVN_ERR(check_scheme_match(adm_access, copyfrom_url));
SVN_ERR(svn_io_check_path(path, &kind, subpool));
switch (kind)
{
case svn_node_none:
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, subpool));
if (entry && entry->schedule != svn_wc_schedule_delete)
{
if (state)
*state = svn_wc_notify_state_obstructed;
return SVN_NO_ERROR;
}
if (! merge_b->dry_run)
{
SVN_ERR(svn_io_make_dir_recursively(path, subpool));
SVN_ERR(svn_wc_add2(path, adm_access,
copyfrom_url, rev,
merge_b->ctx->cancel_func,
merge_b->ctx->cancel_baton,
NULL, NULL,
subpool));
}
if (merge_b->dry_run)
merge_b->added_path = apr_pstrdup(merge_b->pool, path);
if (state)
*state = svn_wc_notify_state_changed;
break;
case svn_node_dir:
SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, subpool));
if (! entry || (entry && entry->schedule == svn_wc_schedule_delete))
{
if (!merge_b->dry_run)
SVN_ERR(svn_wc_add2(path, adm_access,
copyfrom_url, rev,
merge_b->ctx->cancel_func,
merge_b->ctx->cancel_baton,
NULL, NULL,
subpool));
if (merge_b->dry_run)
merge_b->added_path = apr_pstrdup(merge_b->pool, path);
if (state)
*state = svn_wc_notify_state_changed;
}
else if (state)
{
if (dry_run_deleted_p(merge_b, path))
*state = svn_wc_notify_state_changed;
else
*state = svn_wc_notify_state_obstructed;
}
break;
case svn_node_file:
if (merge_b->dry_run)
merge_b->added_path = NULL;
if (state)
{
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, subpool));
if (entry && dry_run_deleted_p(merge_b, path))
*state = svn_wc_notify_state_changed;
else
*state = svn_wc_notify_state_obstructed;
}
break;
default:
if (merge_b->dry_run)
merge_b->added_path = NULL;
if (state)
*state = svn_wc_notify_state_unknown;
break;
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
typedef struct merge_delete_notify_baton_t
{
svn_client_ctx_t *ctx;
const char *path_skip;
} merge_delete_notify_baton_t;
static void
merge_delete_notify_func(void *baton,
const svn_wc_notify_t *notify,
apr_pool_t *pool)
{
merge_delete_notify_baton_t *mdb = baton;
svn_wc_notify_t *new_notify;
if (strcmp(notify->path, mdb->path_skip) == 0)
return;
if (notify->action == svn_wc_notify_delete)
{
new_notify = svn_wc_dup_notify(notify, pool);
new_notify->action = svn_wc_notify_update_delete;
notify = new_notify;
}
if (mdb->ctx->notify_func2)
(*mdb->ctx->notify_func2)(mdb->ctx->notify_baton2, notify, pool);
}
static svn_error_t *
merge_dir_deleted(svn_wc_adm_access_t *adm_access,
svn_wc_notify_state_t *state,
const char *path,
void *baton)
{
struct merge_cmd_baton *merge_b = baton;
apr_pool_t *subpool = svn_pool_create(merge_b->pool);
svn_node_kind_t kind;
svn_wc_adm_access_t *parent_access;
const char *parent_path;
svn_error_t *err;
if (! adm_access)
{
if (state)
*state = svn_wc_notify_state_missing;
return SVN_NO_ERROR;
}
SVN_ERR(svn_io_check_path(path, &kind, subpool));
switch (kind)
{
case svn_node_dir:
{
merge_delete_notify_baton_t mdb;
mdb.ctx = merge_b->ctx;
mdb.path_skip = path;
svn_path_split(path, &parent_path, NULL, subpool);
SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent_path,
subpool));
err = svn_client__wc_delete(path, parent_access, merge_b->force,
merge_b->dry_run,
merge_delete_notify_func, &mdb,
merge_b->ctx, subpool);
if (err && state)
{
*state = svn_wc_notify_state_obstructed;
svn_error_clear(err);
}
else if (state)
{
*state = svn_wc_notify_state_changed;
}
}
break;
case svn_node_file:
if (state)
*state = svn_wc_notify_state_obstructed;
break;
case svn_node_none:
if (state)
*state = svn_wc_notify_state_missing;
break;
default:
if (state)
*state = svn_wc_notify_state_unknown;
break;
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static const svn_wc_diff_callbacks2_t
merge_callbacks =
{
merge_file_changed,
merge_file_added,
merge_file_deleted,
merge_dir_added,
merge_dir_deleted,
merge_props_changed
};
static svn_error_t *
convert_to_url(const char **url,
const char *path,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
if (svn_path_is_url(path))
{
*url = path;
return SVN_NO_ERROR;
}
SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path, FALSE,
0, NULL, NULL, pool));
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc_adm_close(adm_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 (entry->url)
*url = apr_pstrdup(pool, entry->url);
else
*url = apr_pstrdup(pool, entry->copyfrom_url);
return SVN_NO_ERROR;
}
struct diff_parameters
{
const apr_array_header_t *options;
const char *path1;
const svn_opt_revision_t *revision1;
const char *path2;
const svn_opt_revision_t *revision2;
const svn_opt_revision_t *peg_revision;
svn_boolean_t recurse;
svn_boolean_t ignore_ancestry;
svn_boolean_t no_diff_deleted;
};
struct diff_paths
{
svn_boolean_t is_repos1;
svn_boolean_t is_repos2;
};
static svn_error_t *
check_paths(const struct diff_parameters *params,
struct diff_paths *paths)
{
svn_boolean_t is_local_rev1, is_local_rev2;
if ((params->revision1->kind == svn_opt_revision_unspecified)
|| (params->revision2->kind == svn_opt_revision_unspecified))
return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
_("Not all required revisions are specified"));
is_local_rev1 =
((params->revision1->kind == svn_opt_revision_base)
|| (params->revision1->kind == svn_opt_revision_working));
is_local_rev2 =
((params->revision2->kind == svn_opt_revision_base)
|| (params->revision2->kind == svn_opt_revision_working));
if (params->peg_revision->kind != svn_opt_revision_unspecified)
{
if (is_local_rev1 && is_local_rev2)
return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
_("At least one revision must be non-local "
"for a pegged diff"));
paths->is_repos1 = ! is_local_rev1;
paths->is_repos2 = ! is_local_rev2;
}
else
{
paths->is_repos1 = ! is_local_rev1 || svn_path_is_url(params->path1);
paths->is_repos2 = ! is_local_rev2 || svn_path_is_url(params->path2);
}
return SVN_NO_ERROR;
}
struct diff_repos_repos_t
{
const char *url1;
const char *url2;
const char *base_path;
svn_boolean_t same_urls;
svn_revnum_t rev1;
svn_revnum_t rev2;
const char *anchor1;
const char *anchor2;
const char *target1;
const char *target2;
svn_ra_session_t *ra_session;
};
static svn_error_t *
diff_prepare_repos_repos(const struct diff_parameters *params,
struct diff_repos_repos_t *drr,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_session_t *ra_session;
svn_node_kind_t kind1, kind2;
SVN_ERR(convert_to_url(&drr->url1, params->path1, pool));
SVN_ERR(convert_to_url(&drr->url2, params->path2, pool));
drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
drr->base_path = NULL;
if (drr->url1 != params->path1)
drr->base_path = params->path1;
if (drr->url2 != params->path2)
drr->base_path = params->path2;
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, drr->url2,
NULL, NULL, NULL, FALSE,
TRUE, ctx, pool));
if (params->peg_revision->kind != svn_opt_revision_unspecified)
{
svn_opt_revision_t *start_ignore, *end_ignore;
SVN_ERR(svn_client__repos_locations(&drr->url1, &start_ignore,
&drr->url2, &end_ignore,
ra_session,
params->path2,
params->peg_revision,
params->revision1,
params->revision2,
ctx, pool));
SVN_ERR(svn_ra_reparent(ra_session, drr->url2, pool));
}
SVN_ERR(svn_client__get_revision_number
(&drr->rev2, ra_session, params->revision2,
(params->path2 == drr->url2) ? NULL : params->path2, pool));
SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev2, &kind2, pool));
if (kind2 == svn_node_none)
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, NULL,
_("'%s' was not found in the repository at revision %ld"),
drr->url2, drr->rev2);
SVN_ERR(svn_ra_reparent(ra_session, drr->url1, pool));
SVN_ERR(svn_client__get_revision_number
(&drr->rev1, ra_session, params->revision1,
(params->path1 == drr->url1) ? NULL : params->path1, pool));
SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev1, &kind1, pool));
if (kind1 == svn_node_none)
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, NULL,
_("'%s' was not found in the repository at revision %ld"),
drr->url1, drr->rev1);
drr->anchor1 = drr->url1;
drr->anchor2 = drr->url2;
drr->target1 = "";
drr->target2 = "";
if ((kind1 == svn_node_file) || (kind2 == svn_node_file))
{
svn_path_split(drr->url1, &drr->anchor1, &drr->target1, pool);
drr->target1 = svn_path_uri_decode(drr->target1, pool);
svn_path_split(drr->url2, &drr->anchor2, &drr->target2, pool);
drr->target2 = svn_path_uri_decode(drr->target2, pool);
if (drr->base_path)
drr->base_path = svn_path_dirname(drr->base_path, pool);
SVN_ERR(svn_ra_reparent(ra_session, drr->anchor1, pool));
}
drr->ra_session = ra_session;
return SVN_NO_ERROR;
}
static svn_error_t *
do_merge(const char *initial_URL1,
const char *initial_path1,
const svn_opt_revision_t *initial_revision1,
const char *initial_URL2,
const char *initial_path2,
const svn_opt_revision_t *initial_revision2,
const svn_opt_revision_t *peg_revision,
const char *target_wcpath,
svn_wc_adm_access_t *adm_access,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t dry_run,
const svn_wc_diff_callbacks2_t *callbacks,
void *callback_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_revnum_t start_revnum, end_revnum;
svn_ra_session_t *ra_session, *ra_session2;
const svn_ra_reporter2_t *reporter;
void *report_baton;
const svn_delta_editor_t *diff_editor;
void *diff_edit_baton;
struct merge_cmd_baton *merge_b = callback_baton;
const char *URL1, *URL2, *path1, *path2;
svn_opt_revision_t *revision1, *revision2;
if ((initial_revision1->kind == svn_opt_revision_unspecified)
|| (initial_revision2->kind == svn_opt_revision_unspecified))
{
return svn_error_create
(SVN_ERR_CLIENT_BAD_REVISION, NULL,
_("Not all required revisions are specified"));
}
if (peg_revision->kind != svn_opt_revision_unspecified)
{
SVN_ERR(svn_client__repos_locations(&URL1, &revision1,
&URL2, &revision2,
NULL,
initial_path2 ? initial_path2
: initial_URL2,
peg_revision,
initial_revision1,
initial_revision2,
ctx, pool));
merge_b->url = URL2;
path1 = NULL;
path2 = NULL;
merge_b->path = NULL;
}
else
{
URL1 = initial_URL1;
URL2 = initial_URL2;
path1 = initial_path1;
path2 = initial_path2;
revision1 = apr_pcalloc(pool, sizeof(*revision1));
*revision1 = *initial_revision1;
revision2 = apr_pcalloc(pool, sizeof(*revision2));
*revision2 = *initial_revision2;
}
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL1, NULL,
NULL, NULL, FALSE, TRUE,
ctx, pool));
SVN_ERR(svn_client__get_revision_number
(&start_revnum, ra_session, revision1, path1, pool));
SVN_ERR(svn_client__get_revision_number
(&end_revnum, ra_session, revision2, path2, pool));
SVN_ERR(svn_client__open_ra_session_internal(&ra_session2, URL1, NULL,
NULL, NULL, FALSE, TRUE,
ctx, pool));
SVN_ERR(svn_client__get_diff_editor(target_wcpath,
adm_access,
callbacks,
callback_baton,
recurse,
dry_run,
ra_session2,
start_revnum,
ctx->notify_func2,
ctx->notify_baton2,
ctx->cancel_func,
ctx->cancel_baton,
&diff_editor,
&diff_edit_baton,
pool));
SVN_ERR(svn_ra_do_diff2(ra_session,
&reporter, &report_baton,
end_revnum,
"",
recurse,
ignore_ancestry,
TRUE,
URL2,
diff_editor, diff_edit_baton, pool));
SVN_ERR(reporter->set_path(report_baton, "", start_revnum, FALSE, NULL,
pool));
SVN_ERR(reporter->finish_report(report_baton, pool));
svn_sleep_for_timestamps();
return SVN_NO_ERROR;
}
static svn_error_t *
single_file_merge_get_file(const char **filename,
apr_hash_t **props,
svn_revnum_t *rev,
const char *url,
const char *path,
const svn_opt_revision_t *revision,
struct merge_cmd_baton *merge_b,
apr_pool_t *pool)
{
svn_ra_session_t *ra_session;
apr_file_t *fp;
svn_stream_t *stream;
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url, NULL,
NULL, NULL, FALSE, TRUE,
merge_b->ctx, pool));
SVN_ERR(svn_client__get_revision_number(rev, ra_session, revision,
path, pool));
SVN_ERR(svn_io_open_unique_file2(&fp, filename,
merge_b->target, ".tmp",
svn_io_file_del_none, pool));
stream = svn_stream_from_aprfile2(fp, FALSE, pool);
SVN_ERR(svn_ra_get_file(ra_session, "", *rev,
stream, NULL, props, pool));
SVN_ERR(svn_stream_close(stream));
return SVN_NO_ERROR;
}
static svn_error_t *
do_single_file_merge(const char *initial_URL1,
const char *initial_path1,
const svn_opt_revision_t *initial_revision1,
const char *initial_URL2,
const char *initial_path2,
const svn_opt_revision_t *initial_revision2,
const svn_opt_revision_t *peg_revision,
const char *target_wcpath,
svn_wc_adm_access_t *adm_access,
struct merge_cmd_baton *merge_b,
apr_pool_t *pool)
{
apr_hash_t *props1, *props2;
const char *tmpfile1, *tmpfile2;
svn_revnum_t rev1, rev2;
const char *mimetype1, *mimetype2;
svn_string_t *pval;
apr_array_header_t *propchanges;
svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
svn_wc_notify_state_t text_state = svn_wc_notify_state_unknown;
const char *URL1, *path1, *URL2, *path2;
svn_opt_revision_t *revision1, *revision2;
svn_error_t *err;
if (peg_revision->kind != svn_opt_revision_unspecified)
{
SVN_ERR(svn_client__repos_locations(&URL1, &revision1,
&URL2, &revision2,
NULL,
initial_path2 ? initial_path2
: initial_URL2,
peg_revision,
initial_revision1,
initial_revision2,
merge_b->ctx, pool));
merge_b->url = URL2;
merge_b->path = NULL;
path1 = NULL;
path2 = NULL;
}
else
{
URL1 = initial_URL1;
URL2 = initial_URL2;
path1 = initial_path1;
path2 = initial_path2;
revision1 = apr_pcalloc(pool, sizeof(*revision1));
*revision1 = *initial_revision1;
revision2 = apr_pcalloc(pool, sizeof(*revision2));
*revision2 = *initial_revision2;
}
SVN_ERR(single_file_merge_get_file(&tmpfile1, &props1, &rev1,
URL1, path1, revision1,
merge_b, pool));
SVN_ERR(single_file_merge_get_file(&tmpfile2, &props2, &rev2,
URL2, path2, revision2,
merge_b, pool));
pval = apr_hash_get(props1, SVN_PROP_MIME_TYPE, strlen(SVN_PROP_MIME_TYPE));
mimetype1 = pval ? pval->data : NULL;
pval = apr_hash_get(props2, SVN_PROP_MIME_TYPE, strlen(SVN_PROP_MIME_TYPE));
mimetype2 = pval ? pval->data : NULL;
SVN_ERR(svn_prop_diffs(&propchanges, props2, props1, pool));
SVN_ERR(merge_file_changed(adm_access,
&text_state, &prop_state,
merge_b->target,
tmpfile1,
tmpfile2,
rev1,
rev2,
mimetype1, mimetype2,
propchanges, props1,
merge_b));
err = svn_io_remove_file(tmpfile1, pool);
if (err && ! APR_STATUS_IS_ENOENT(err->apr_err))
return err;
svn_error_clear(err);
err = svn_io_remove_file(tmpfile2, pool);
if (err && ! APR_STATUS_IS_ENOENT(err->apr_err))
return err;
svn_error_clear(err);
if (merge_b->ctx->notify_func2)
{
svn_wc_notify_t *notify
= svn_wc_create_notify(merge_b->target, svn_wc_notify_update_update,
pool);
notify->kind = svn_node_file;
notify->content_state = text_state;
notify->prop_state = prop_state;
(*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
pool);
}
svn_sleep_for_timestamps();
return SVN_NO_ERROR;
}
static svn_error_t *
unsupported_diff_error(svn_error_t *child_err)
{
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
_("Sorry, svn_client_diff3 was called in a way "
"that is not yet supported"));
}
static svn_error_t *
diff_wc_wc(const apr_array_header_t *options,
const char *path1,
const svn_opt_revision_t *revision1,
const char *path2,
const svn_opt_revision_t *revision2,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
const svn_wc_diff_callbacks2_t *callbacks,
struct diff_cmd_baton *callback_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access, *target_access;
const char *target;
assert(! svn_path_is_url(path1));
assert(! svn_path_is_url(path2));
if ((strcmp(path1, path2) != 0)
|| (! ((revision1->kind == svn_opt_revision_base)
&& (revision2->kind == svn_opt_revision_working))))
return unsupported_diff_error
(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")));
SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &target_access, &target,
path1, FALSE, recurse ? -1 : 0,
ctx->cancel_func, ctx->cancel_baton,
pool));
SVN_ERR(svn_client__get_revision_number
(&callback_baton->revnum1, NULL, revision1, path1, pool));
callback_baton->revnum2 = SVN_INVALID_REVNUM;
SVN_ERR(svn_wc_diff3(adm_access, target, callbacks, callback_baton,
recurse, ignore_ancestry, pool));
SVN_ERR(svn_wc_adm_close(adm_access));
return SVN_NO_ERROR;
}
static svn_error_t *
diff_repos_repos(const struct diff_parameters *diff_param,
const svn_wc_diff_callbacks2_t *callbacks,
struct diff_cmd_baton *callback_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_session_t *extra_ra_session;
const svn_ra_reporter2_t *reporter;
void *report_baton;
const svn_delta_editor_t *diff_editor;
void *diff_edit_baton;
struct diff_repos_repos_t drr;
SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
callback_baton->orig_path_1 = drr.url1;
callback_baton->orig_path_2 = drr.url2;
callback_baton->revnum1 = drr.rev1;
callback_baton->revnum2 = drr.rev2;
SVN_ERR(svn_client__open_ra_session_internal
(&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE, ctx,
pool));
SVN_ERR(svn_client__get_diff_editor
(drr.base_path ? drr.base_path : "",
NULL, callbacks, callback_baton, diff_param->recurse,
FALSE , extra_ra_session, drr.rev1,
NULL , NULL ,
ctx->cancel_func, ctx->cancel_baton,
&diff_editor, &diff_edit_baton, pool));
SVN_ERR(svn_ra_do_diff2
(drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
diff_param->recurse, diff_param->ignore_ancestry, TRUE,
drr.url2, diff_editor, diff_edit_baton, pool));
SVN_ERR(reporter->set_path(report_baton, "", drr.rev1, FALSE, NULL,
pool));
SVN_ERR(reporter->finish_report(report_baton, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
diff_repos_wc(const apr_array_header_t *options,
const char *path1,
const svn_opt_revision_t *revision1,
const svn_opt_revision_t *peg_revision,
const char *path2,
const svn_opt_revision_t *revision2,
svn_boolean_t reverse,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
const svn_wc_diff_callbacks2_t *callbacks,
struct diff_cmd_baton *callback_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
const char *url1, *anchor, *anchor_url, *target;
svn_wc_adm_access_t *adm_access, *dir_access;
const svn_wc_entry_t *entry;
svn_revnum_t rev;
svn_ra_session_t *ra_session;
const svn_ra_reporter2_t *reporter;
void *report_baton;
const svn_delta_editor_t *diff_editor;
void *diff_edit_baton;
svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
assert(! svn_path_is_url(path2));
SVN_ERR(convert_to_url(&url1, path1, pool));
SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target,
path2, FALSE, recurse ? -1 : 0,
ctx->cancel_func, ctx->cancel_baton,
pool));
anchor = svn_wc_adm_access_path(adm_access);
SVN_ERR(svn_wc_entry(&entry, anchor, adm_access, FALSE, pool));
if (! entry)
return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
svn_path_local_style(anchor, pool));
if (! entry->url)
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("Directory '%s' has no URL"),
svn_path_local_style(anchor, pool));
anchor_url = apr_pstrdup(pool, entry->url);
if (peg_revision->kind != svn_opt_revision_unspecified)
{
svn_opt_revision_t *start_ignore, *end_ignore, end;
const char *url_ignore;
end.kind = svn_opt_revision_unspecified;
SVN_ERR(svn_client__repos_locations(&url1, &start_ignore,
&url_ignore, &end_ignore,
NULL,
path1,
peg_revision,
revision1, &end,
ctx, pool));
if (!reverse)
{
callback_baton->orig_path_1 = url1;
callback_baton->orig_path_2 = svn_path_join(anchor_url, target, pool);
}
else
{
callback_baton->orig_path_1 = svn_path_join(anchor_url, target, pool);
callback_baton->orig_path_2 = url1;
}
}
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, anchor_url,
NULL, NULL, NULL, FALSE, TRUE,
ctx, pool));
SVN_ERR(svn_wc_get_diff_editor3(adm_access, target,
callbacks, callback_baton,
recurse,
ignore_ancestry,
rev2_is_base,
reverse,
ctx->cancel_func, ctx->cancel_baton,
&diff_editor, &diff_edit_baton,
pool));
SVN_ERR(svn_client__get_revision_number
(&rev, ra_session, revision1,
(path1 == url1) ? NULL : path1, pool));
if (!reverse)
callback_baton->revnum1 = rev;
else
callback_baton->revnum2 = rev;
SVN_ERR(svn_ra_do_diff2(ra_session,
&reporter, &report_baton,
rev,
target ? svn_path_uri_decode(target, pool) : NULL,
recurse,
ignore_ancestry,
TRUE,
url1,
diff_editor, diff_edit_baton, pool));
SVN_ERR(svn_wc_crawl_revisions2(path2, dir_access,
reporter, report_baton,
FALSE, recurse, FALSE,
NULL, NULL,
NULL, pool));
SVN_ERR(svn_wc_adm_close(adm_access));
return SVN_NO_ERROR;
}
static svn_error_t *
do_diff(const struct diff_parameters *diff_param,
const svn_wc_diff_callbacks2_t *callbacks,
struct diff_cmd_baton *callback_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct diff_paths diff_paths;
SVN_ERR(check_paths(diff_param, &diff_paths));
if (diff_paths.is_repos1)
{
if (diff_paths.is_repos2)
{
SVN_ERR(diff_repos_repos(diff_param, callbacks, callback_baton,
ctx, pool));
}
else
{
SVN_ERR(diff_repos_wc(diff_param->options,
diff_param->path1, diff_param->revision1,
diff_param->peg_revision,
diff_param->path2, diff_param->revision2,
FALSE, diff_param->recurse,
diff_param->ignore_ancestry,
callbacks, callback_baton, ctx, pool));
}
}
else
{
if (diff_paths.is_repos2)
{
SVN_ERR(diff_repos_wc(diff_param->options,
diff_param->path2, diff_param->revision2,
diff_param->peg_revision,
diff_param->path1, diff_param->revision1,
TRUE, diff_param->recurse,
diff_param->ignore_ancestry,
callbacks, callback_baton, ctx, pool));
}
else
{
SVN_ERR(diff_wc_wc(diff_param->options,
diff_param->path1, diff_param->revision1,
diff_param->path2, diff_param->revision2,
diff_param->recurse,
diff_param->ignore_ancestry,
callbacks, callback_baton, ctx, pool));
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
diff_summarize_repos_repos(const struct diff_parameters *diff_param,
svn_client_diff_summarize_func_t summarize_func,
void *summarize_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_session_t *extra_ra_session;
const svn_ra_reporter2_t *reporter;
void *report_baton;
const svn_delta_editor_t *diff_editor;
void *diff_edit_baton;
struct diff_repos_repos_t drr;
SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
SVN_ERR(svn_client__open_ra_session_internal
(&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE,
ctx, pool));
SVN_ERR(svn_client__get_diff_summarize_editor
(drr.base_path ? drr.base_path : "", summarize_func,
summarize_baton, extra_ra_session, drr.rev1, ctx->cancel_func,
ctx->cancel_baton, &diff_editor, &diff_edit_baton, pool));
SVN_ERR(svn_ra_do_diff2
(drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
diff_param->recurse, diff_param->ignore_ancestry,
FALSE , drr.url2, diff_editor,
diff_edit_baton, pool));
SVN_ERR(reporter->set_path(report_baton, "", drr.rev1, FALSE, NULL,
pool));
SVN_ERR(reporter->finish_report(report_baton, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
do_diff_summarize(const struct diff_parameters *diff_param,
svn_client_diff_summarize_func_t summarize_func,
void *summarize_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct diff_paths diff_paths;
SVN_ERR(check_paths(diff_param, &diff_paths));
if (diff_paths.is_repos1 && diff_paths.is_repos2)
{
SVN_ERR(diff_summarize_repos_repos(diff_param, summarize_func,
summarize_baton, ctx, pool));
}
else
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Summarizing diff can only compare repository "
"to repository"));
return SVN_NO_ERROR;
}
svn_client_diff_summarize_t *
svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
apr_pool_t *pool)
{
svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
*dup_diff = *diff;
if (diff->path)
dup_diff->path = apr_pstrdup(pool, diff->path);
return dup_diff;
}
svn_error_t *
svn_client_diff3(const apr_array_header_t *options,
const char *path1,
const svn_opt_revision_t *revision1,
const char *path2,
const svn_opt_revision_t *revision2,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t no_diff_deleted,
svn_boolean_t ignore_content_type,
const char *header_encoding,
apr_file_t *outfile,
apr_file_t *errfile,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct diff_parameters diff_params;
struct diff_cmd_baton diff_cmd_baton;
svn_wc_diff_callbacks2_t diff_callbacks;
svn_opt_revision_t peg_revision;
peg_revision.kind = svn_opt_revision_unspecified;
diff_params.options = options;
diff_params.path1 = path1;
diff_params.revision1 = revision1;
diff_params.path2 = path2;
diff_params.revision2 = revision2;
diff_params.peg_revision = &peg_revision;
diff_params.recurse = recurse;
diff_params.ignore_ancestry = ignore_ancestry;
diff_params.no_diff_deleted = no_diff_deleted;
diff_callbacks.file_changed = diff_file_changed;
diff_callbacks.file_added = diff_file_added;
diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
diff_file_deleted_with_diff;
diff_callbacks.dir_added = diff_dir_added;
diff_callbacks.dir_deleted = diff_dir_deleted;
diff_callbacks.dir_props_changed = diff_props_changed;
diff_cmd_baton.orig_path_1 = path1;
diff_cmd_baton.orig_path_2 = path2;
diff_cmd_baton.options = options;
diff_cmd_baton.pool = pool;
diff_cmd_baton.outfile = outfile;
diff_cmd_baton.errfile = errfile;
diff_cmd_baton.header_encoding = header_encoding;
diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
diff_cmd_baton.config = ctx->config;
diff_cmd_baton.force_empty = FALSE;
diff_cmd_baton.force_binary = ignore_content_type;
return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
}
svn_error_t *
svn_client_diff2(const apr_array_header_t *options,
const char *path1,
const svn_opt_revision_t *revision1,
const char *path2,
const svn_opt_revision_t *revision2,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t no_diff_deleted,
svn_boolean_t ignore_content_type,
apr_file_t *outfile,
apr_file_t *errfile,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_diff3(options, path1, revision1, path2, revision2,
recurse, ignore_ancestry, no_diff_deleted,
ignore_content_type, SVN_APR_LOCALE_CHARSET,
outfile, errfile, ctx, pool);
}
svn_error_t *
svn_client_diff(const apr_array_header_t *options,
const char *path1,
const svn_opt_revision_t *revision1,
const char *path2,
const svn_opt_revision_t *revision2,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t no_diff_deleted,
apr_file_t *outfile,
apr_file_t *errfile,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_diff2(options, path1, revision1, path2, revision2,
recurse, ignore_ancestry, no_diff_deleted, FALSE,
outfile, errfile, ctx, pool);
}
svn_error_t *
svn_client_diff_peg3(const apr_array_header_t *options,
const char *path,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *start_revision,
const svn_opt_revision_t *end_revision,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t no_diff_deleted,
svn_boolean_t ignore_content_type,
const char *header_encoding,
apr_file_t *outfile,
apr_file_t *errfile,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct diff_parameters diff_params;
struct diff_cmd_baton diff_cmd_baton;
svn_wc_diff_callbacks2_t diff_callbacks;
diff_params.options = options;
diff_params.path1 = path;
diff_params.revision1 = start_revision;
diff_params.path2 = path;
diff_params.revision2 = end_revision;
diff_params.peg_revision = peg_revision;
diff_params.recurse = recurse;
diff_params.ignore_ancestry = ignore_ancestry;
diff_params.no_diff_deleted = no_diff_deleted;
diff_callbacks.file_changed = diff_file_changed;
diff_callbacks.file_added = diff_file_added;
diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
diff_file_deleted_with_diff;
diff_callbacks.dir_added = diff_dir_added;
diff_callbacks.dir_deleted = diff_dir_deleted;
diff_callbacks.dir_props_changed = diff_props_changed;
diff_cmd_baton.orig_path_1 = path;
diff_cmd_baton.orig_path_2 = path;
diff_cmd_baton.options = options;
diff_cmd_baton.pool = pool;
diff_cmd_baton.outfile = outfile;
diff_cmd_baton.errfile = errfile;
diff_cmd_baton.header_encoding = header_encoding;
diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
diff_cmd_baton.config = ctx->config;
diff_cmd_baton.force_empty = FALSE;
diff_cmd_baton.force_binary = ignore_content_type;
return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
}
svn_error_t *
svn_client_diff_peg2(const apr_array_header_t *options,
const char *path,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *start_revision,
const svn_opt_revision_t *end_revision,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t no_diff_deleted,
svn_boolean_t ignore_content_type,
apr_file_t *outfile,
apr_file_t *errfile,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_diff_peg3(options, path, peg_revision, start_revision,
end_revision, recurse, ignore_ancestry,
no_diff_deleted, ignore_content_type,
SVN_APR_LOCALE_CHARSET, outfile, errfile,
ctx, pool);
}
svn_error_t *
svn_client_diff_peg(const apr_array_header_t *options,
const char *path,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *start_revision,
const svn_opt_revision_t *end_revision,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t no_diff_deleted,
apr_file_t *outfile,
apr_file_t *errfile,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_diff_peg2(options, path, peg_revision,
start_revision, end_revision, recurse,
ignore_ancestry, no_diff_deleted, FALSE,
outfile, errfile, ctx, pool);
}
svn_error_t *
svn_client_diff_summarize(const char *path1,
const svn_opt_revision_t *revision1,
const char *path2,
const svn_opt_revision_t *revision2,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_client_diff_summarize_func_t summarize_func,
void *summarize_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct diff_parameters diff_params;
svn_opt_revision_t peg_revision;
peg_revision.kind = svn_opt_revision_unspecified;
diff_params.options = NULL;
diff_params.path1 = path1;
diff_params.revision1 = revision1;
diff_params.path2 = path2;
diff_params.revision2 = revision2;
diff_params.peg_revision = &peg_revision;
diff_params.recurse = recurse;
diff_params.ignore_ancestry = ignore_ancestry;
diff_params.no_diff_deleted = FALSE;
return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
ctx, pool);
}
svn_error_t *
svn_client_diff_summarize_peg(const char *path,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *start_revision,
const svn_opt_revision_t *end_revision,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_client_diff_summarize_func_t summarize_func,
void *summarize_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct diff_parameters diff_params;
diff_params.options = NULL;
diff_params.path1 = path;
diff_params.revision1 = start_revision;
diff_params.path2 = path;
diff_params.revision2 = end_revision;
diff_params.peg_revision = peg_revision;
diff_params.recurse = recurse;
diff_params.ignore_ancestry = ignore_ancestry;
diff_params.no_diff_deleted = FALSE;
return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
ctx, pool);
}
svn_error_t *
svn_client_merge2(const char *source1,
const svn_opt_revision_t *revision1,
const char *source2,
const svn_opt_revision_t *revision2,
const char *target_wcpath,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t force,
svn_boolean_t dry_run,
const apr_array_header_t *merge_options,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
struct merge_cmd_baton merge_cmd_baton;
const char *URL1, *URL2;
const char *path1, *path2;
svn_opt_revision_t peg_revision;
peg_revision.kind = svn_opt_revision_unspecified;
SVN_ERR(svn_client_url_from_path(&URL1, source1, pool));
if (! URL1)
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' has no URL"),
svn_path_local_style(source1, pool));
SVN_ERR(svn_client_url_from_path(&URL2, source2, pool));
if (! URL2)
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' has no URL"),
svn_path_local_style(source2, pool));
if (URL1 == source1)
path1 = NULL;
else
path1 = source1;
if (URL2 == source2)
path2 = NULL;
else
path2 = source2;
SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, target_wcpath,
! dry_run, recurse ? -1 : 0,
ctx->cancel_func, ctx->cancel_baton,
pool));
SVN_ERR(svn_wc_entry(&entry, target_wcpath, adm_access, FALSE, pool));
if (entry == NULL)
return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
svn_path_local_style(target_wcpath, pool));
merge_cmd_baton.force = force;
merge_cmd_baton.dry_run = dry_run;
merge_cmd_baton.merge_options = merge_options;
merge_cmd_baton.target = target_wcpath;
merge_cmd_baton.url = URL2;
merge_cmd_baton.revision = revision2;
merge_cmd_baton.path = path2;
merge_cmd_baton.added_path = NULL;
merge_cmd_baton.add_necessitated_merge = FALSE;
merge_cmd_baton.dry_run_deletions = (dry_run ? apr_hash_make(pool) : NULL);
merge_cmd_baton.ctx = ctx;
merge_cmd_baton.pool = pool;
{
svn_config_t *cfg =
ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING) : NULL;
svn_config_get(cfg, &(merge_cmd_baton.diff3_cmd),
SVN_CONFIG_SECTION_HELPERS,
SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
}
if (entry->kind == svn_node_file)
{
SVN_ERR(do_single_file_merge(URL1, path1, revision1,
URL2, path2, revision2,
&peg_revision,
target_wcpath,
adm_access,
&merge_cmd_baton,
pool));
}
else if (entry->kind == svn_node_dir)
{
SVN_ERR(do_merge(URL1,
path1,
revision1,
URL2,
merge_cmd_baton.path,
revision2,
&peg_revision,
target_wcpath,
adm_access,
recurse,
ignore_ancestry,
dry_run,
&merge_callbacks,
&merge_cmd_baton,
ctx,
pool));
}
SVN_ERR(svn_wc_adm_close(adm_access));
return SVN_NO_ERROR;
}
svn_error_t *
svn_client_merge(const char *source1,
const svn_opt_revision_t *revision1,
const char *source2,
const svn_opt_revision_t *revision2,
const char *target_wcpath,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t force,
svn_boolean_t dry_run,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_merge2(source1, revision1, source2, revision2,
target_wcpath, recurse, ignore_ancestry, force,
dry_run, NULL, ctx, pool);
}
svn_error_t *
svn_client_merge_peg2(const char *source,
const svn_opt_revision_t *revision1,
const svn_opt_revision_t *revision2,
const svn_opt_revision_t *peg_revision,
const char *target_wcpath,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t force,
svn_boolean_t dry_run,
const apr_array_header_t *merge_options,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
struct merge_cmd_baton merge_cmd_baton;
const char *URL;
const char *path;
SVN_ERR(svn_client_url_from_path(&URL, source, pool));
if (! URL)
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' has no URL"),
svn_path_local_style(source, pool));
if (URL == source)
path = NULL;
else
path = source;
SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, target_wcpath,
! dry_run, recurse ? -1 : 0,
ctx->cancel_func, ctx->cancel_baton,
pool));
SVN_ERR(svn_wc_entry(&entry, target_wcpath, adm_access, FALSE, pool));
if (entry == NULL)
return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
svn_path_local_style(target_wcpath, pool));
merge_cmd_baton.force = force;
merge_cmd_baton.dry_run = dry_run;
merge_cmd_baton.merge_options = merge_options;
merge_cmd_baton.target = target_wcpath;
merge_cmd_baton.url = URL;
merge_cmd_baton.revision = revision2;
merge_cmd_baton.path = path;
merge_cmd_baton.added_path = NULL;
merge_cmd_baton.add_necessitated_merge = FALSE;
merge_cmd_baton.dry_run_deletions = (dry_run ? apr_hash_make(pool) : NULL);
merge_cmd_baton.ctx = ctx;
merge_cmd_baton.pool = pool;
{
svn_config_t *cfg =
ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING) : NULL;
svn_config_get(cfg, &(merge_cmd_baton.diff3_cmd),
SVN_CONFIG_SECTION_HELPERS,
SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
}
if (entry->kind == svn_node_file)
{
SVN_ERR(do_single_file_merge(URL, path, revision1,
URL, path, revision2,
peg_revision,
target_wcpath,
adm_access,
&merge_cmd_baton,
pool));
}
else if (entry->kind == svn_node_dir)
{
SVN_ERR(do_merge(URL,
path,
revision1,
URL,
path,
revision2,
peg_revision,
target_wcpath,
adm_access,
recurse,
ignore_ancestry,
dry_run,
&merge_callbacks,
&merge_cmd_baton,
ctx,
pool));
}
SVN_ERR(svn_wc_adm_close(adm_access));
return SVN_NO_ERROR;
}
svn_error_t *
svn_client_merge_peg(const char *source,
const svn_opt_revision_t *revision1,
const svn_opt_revision_t *revision2,
const svn_opt_revision_t *peg_revision,
const char *target_wcpath,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t force,
svn_boolean_t dry_run,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_merge_peg2(source, revision1, revision2, peg_revision,
target_wcpath, recurse, ignore_ancestry, force,
dry_run, NULL, ctx, pool);
}