#include "svn_client.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_error.h"
#include "svn_types.h"
#include "cl.h"
#include "private/svn_client_private.h"
#include "svn_private_config.h"
static svn_error_t *
ensure_wc_path_has_repo_revision(const char *path_or_url,
const svn_opt_revision_t *revision,
apr_pool_t *scratch_pool)
{
if (revision->kind != svn_opt_revision_number
&& revision->kind != svn_opt_revision_date
&& revision->kind != svn_opt_revision_head
&& ! svn_path_is_url(path_or_url))
return svn_error_createf(
SVN_ERR_CLIENT_BAD_REVISION, NULL,
_("Invalid merge source '%s'; a working copy path can only be "
"used with a repository revision (a number, a date, or head)"),
svn_dirent_local_style(path_or_url, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
run_merge(svn_boolean_t two_sources_specified,
const char *sourcepath1,
svn_opt_revision_t peg_revision1,
const char *sourcepath2,
const char *targetpath,
apr_array_header_t *ranges_to_merge,
svn_opt_revision_t first_range_start,
svn_opt_revision_t first_range_end,
svn_cl__opt_state_t *opt_state,
apr_array_header_t *options,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
svn_error_t *merge_err;
if (opt_state->reintegrate)
{
merge_err = svn_cl__deprecated_merge_reintegrate(
sourcepath1, &peg_revision1, targetpath,
opt_state->dry_run, options, ctx, scratch_pool);
}
else if (! two_sources_specified)
{
if ((first_range_start.kind == svn_opt_revision_unspecified)
&& (first_range_end.kind == svn_opt_revision_unspecified))
{
ranges_to_merge = NULL;
}
if (opt_state->verbose)
SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
merge_err = svn_client_merge_peg5(sourcepath1,
ranges_to_merge,
&peg_revision1,
targetpath,
opt_state->depth,
opt_state->ignore_ancestry,
opt_state->ignore_ancestry,
opt_state->force,
opt_state->record_only,
opt_state->dry_run,
opt_state->allow_mixed_rev,
options,
ctx,
scratch_pool);
}
else
{
if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2))
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Merge sources must both be "
"either paths or URLs"));
if (svn_path_is_url(targetpath))
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Merge target '%s' must be a local path "
"but looks like a URL"), targetpath);
if (opt_state->verbose)
SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
merge_err = svn_client_merge5(sourcepath1,
&first_range_start,
sourcepath2,
&first_range_end,
targetpath,
opt_state->depth,
opt_state->ignore_ancestry,
opt_state->ignore_ancestry,
opt_state->force,
opt_state->record_only,
opt_state->dry_run,
opt_state->allow_mixed_rev,
options,
ctx,
scratch_pool);
}
return merge_err;
}
struct conflict_func_merge_cmd_baton {
svn_cl__accept_t accept_which;
const char *path_prefix;
svn_cl__conflict_stats_t *conflict_stats;
};
static svn_error_t *
conflict_func_merge_cmd(svn_wc_conflict_result_t **result,
const svn_wc_conflict_description2_t *desc,
void *baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct conflict_func_merge_cmd_baton *b = baton;
svn_wc_conflict_choice_t choice;
switch (b->accept_which)
{
case svn_cl__accept_postpone:
case svn_cl__accept_invalid:
case svn_cl__accept_unspecified:
case svn_cl__accept_recommended:
choice = svn_wc_conflict_choose_postpone;
break;
case svn_cl__accept_base:
choice = svn_wc_conflict_choose_base;
break;
case svn_cl__accept_working:
choice = svn_wc_conflict_choose_merged;
break;
case svn_cl__accept_mine_conflict:
choice = svn_wc_conflict_choose_mine_conflict;
break;
case svn_cl__accept_theirs_conflict:
choice = svn_wc_conflict_choose_theirs_conflict;
break;
case svn_cl__accept_mine_full:
choice = svn_wc_conflict_choose_mine_full;
break;
case svn_cl__accept_theirs_full:
choice = svn_wc_conflict_choose_theirs_full;
break;
case svn_cl__accept_edit:
case svn_cl__accept_launch:
choice = svn_wc_conflict_choose_postpone;
break;
}
*result = svn_wc_create_conflict_result(choice, NULL, result_pool);
if (choice != svn_wc_conflict_choose_postpone)
{
const char *local_path;
local_path = svn_cl__local_style_skip_ancestor(b->path_prefix,
desc->local_abspath,
scratch_pool);
svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
desc->kind);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_cl__merge(apr_getopt_t *os,
void *baton,
apr_pool_t *pool)
{
svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
svn_cl__conflict_stats_t *conflict_stats =
((svn_cl__cmd_baton_t *) baton)->conflict_stats;
svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
apr_array_header_t *targets;
const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = "";
svn_boolean_t two_sources_specified = TRUE;
svn_error_t *merge_err;
svn_opt_revision_t first_range_start, first_range_end, peg_revision1,
peg_revision2;
apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
apr_array_header_t *conflicted_paths;
svn_boolean_t has_explicit_target = FALSE;
if (opt_state->reintegrate
&& opt_state->start_revision.kind != svn_opt_revision_unspecified)
{
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("-r and -c can't be used with --reintegrate"));
}
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
ctx, FALSE, pool));
if (targets->nelts < 1)
{
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
_("Merge source required"));
}
else
{
SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
APR_ARRAY_IDX(targets, 0, const char *),
pool));
if (targets->nelts >= 2)
SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
APR_ARRAY_IDX(targets, 1, const char *),
pool));
}
if (targets->nelts <= 1)
{
two_sources_specified = FALSE;
}
else if (targets->nelts == 2)
{
if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
two_sources_specified = FALSE;
}
if (opt_state->revision_ranges->nelts > 0)
{
first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
svn_opt_revision_range_t *)->start;
first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
svn_opt_revision_range_t *)->end;
}
else
{
first_range_start.kind = first_range_end.kind =
svn_opt_revision_unspecified;
}
if (first_range_start.kind != svn_opt_revision_unspecified)
{
if (first_range_end.kind == svn_opt_revision_unspecified)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
_("Second revision required"));
two_sources_specified = FALSE;
}
if (! two_sources_specified)
{
if (targets->nelts > 2)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Too many arguments given"));
sourcepath2 = sourcepath1;
if (peg_revision1.kind == svn_opt_revision_unspecified)
peg_revision1.kind = svn_path_is_url(sourcepath1)
? svn_opt_revision_head : svn_opt_revision_working;
if (targets->nelts == 2)
{
targetpath = APR_ARRAY_IDX(targets, 1, const char *);
has_explicit_target = TRUE;
if (svn_path_is_url(targetpath))
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Cannot specify a revision range "
"with two URLs"));
}
}
else
{
if (targets->nelts < 2)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
if (targets->nelts > 3)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Too many arguments given"));
first_range_start = peg_revision1;
first_range_end = peg_revision2;
SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start,
pool));
SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
pool));
if (first_range_start.kind == svn_opt_revision_unspecified)
first_range_start.kind = svn_opt_revision_head;
if (first_range_end.kind == svn_opt_revision_unspecified)
first_range_end.kind = svn_opt_revision_head;
if (targets->nelts == 3)
{
targetpath = APR_ARRAY_IDX(targets, 2, const char *);
has_explicit_target = TRUE;
}
}
if (! has_explicit_target
&& sourcepath1 && sourcepath2
&& strcmp(targetpath, "") == 0)
{
if (svn_path_is_url(sourcepath1))
{
const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
if (strcmp(sp1_basename, sp2_basename) == 0)
{
const char *target_url;
const char *target_base;
SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
pool, pool));
target_base = svn_uri_basename(target_url, pool);
if (strcmp(sp1_basename, target_base) != 0)
{
svn_node_kind_t kind;
SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool));
if (kind == svn_node_file)
{
targetpath = sp1_basename;
}
}
}
}
else if (strcmp(sourcepath1, sourcepath2) == 0)
{
svn_node_kind_t kind;
SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
if (kind == svn_node_file)
{
targetpath = sourcepath1;
}
}
}
if (opt_state->extensions)
options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
else
options = NULL;
if (opt_state->reintegrate)
{
if (opt_state->ignore_ancestry)
return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
_("--reintegrate cannot be used with "
"--ignore-ancestry"));
if (opt_state->record_only)
return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
_("--reintegrate cannot be used with "
"--record-only"));
if (opt_state->depth != svn_depth_unknown)
return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
_("--depth cannot be used with "
"--reintegrate"));
if (opt_state->force)
return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
_("--force cannot be used with "
"--reintegrate"));
if (two_sources_specified)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--reintegrate can only be used with "
"a single merge source"));
if (opt_state->allow_mixed_rev)
return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
_("--allow-mixed-revisions cannot be used "
"with --reintegrate"));
}
if (opt_state->accept_which != svn_cl__accept_unspecified)
{
struct conflict_func_merge_cmd_baton *b = apr_pcalloc(pool, sizeof(*b));
b->accept_which = opt_state->accept_which;
SVN_ERR(svn_dirent_get_absolute(&b->path_prefix, "", pool));
b->conflict_stats = conflict_stats;
ctx->conflict_func2 = conflict_func_merge_cmd;
ctx->conflict_baton2 = b;
}
retry:
merge_err = run_merge(two_sources_specified,
sourcepath1, peg_revision1,
sourcepath2,
targetpath,
ranges_to_merge, first_range_start, first_range_end,
opt_state, options, ctx, pool);
if (merge_err && merge_err->apr_err
== SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
{
return svn_error_quick_wrap(
merge_err,
_("Merge tracking not possible, use --ignore-ancestry or\n"
"fix invalid mergeinfo in target with 'svn propset'"));
}
SVN_ERR(svn_cl__conflict_stats_get_paths(&conflicted_paths, conflict_stats,
pool, pool));
if (conflicted_paths)
{
SVN_ERR(svn_cl__walk_conflicts(conflicted_paths, conflict_stats,
opt_state, ctx, pool));
if (merge_err &&
svn_error_root_cause(merge_err)->apr_err == SVN_ERR_WC_FOUND_CONFLICT)
{
svn_error_t *err;
err = svn_cl__conflict_stats_get_paths(&conflicted_paths,
conflict_stats, pool, pool);
if (err)
merge_err = svn_error_compose_create(merge_err, err);
else if (conflicted_paths == NULL)
{
svn_error_clear(merge_err);
goto retry;
}
}
}
if (!opt_state->quiet)
{
svn_error_t *err = svn_cl__notifier_print_conflict_stats(
ctx->notify_baton2, pool);
merge_err = svn_error_compose_create(merge_err, err);
}
return svn_cl__may_need_force(merge_err);
}