diff options
Diffstat (limited to 'subversion/svn/merge-cmd.c')
-rw-r--r-- | subversion/svn/merge-cmd.c | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c new file mode 100644 index 0000000..c14f769 --- /dev/null +++ b/subversion/svn/merge-cmd.c @@ -0,0 +1,467 @@ +/* + * merge-cmd.c -- Merging changes into a working copy. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#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" + +/* A handy constant */ +static const svn_opt_revision_t unspecified_revision + = { svn_opt_revision_unspecified, { 0 } }; + + +/*** Code. ***/ + +/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository + * revision. */ +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; +} + +/* Run a merge. + * + * (No docs yet, as this code was just hoisted out of svn_cl__merge().) + * + * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use + * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller. + */ +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 we don't have at least one valid revision range, pick a + good one that spans the entire set of revisions on our + source. */ + if ((first_range_start.kind == svn_opt_revision_unspecified) + && (first_range_end.kind == svn_opt_revision_unspecified)) + { + ranges_to_merge = NULL; + + /* This must be a 'sync' merge so check branch relationship. */ + if (opt_state->verbose) + SVN_ERR(svn_cmdline_printf( + scratch_pool, _("--- Checking branch relationship\n"))); + SVN_ERR_W(svn_cl__check_related_source_and_target( + sourcepath1, &peg_revision1, + targetpath, &unspecified_revision, ctx, scratch_pool), + _("Source and target must be different but related branches")); + } + + 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, /* force_delete */ + 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 (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, /* force_delete */ + opt_state->record_only, + opt_state->dry_run, + opt_state->allow_mixed_rev, + options, + ctx, + scratch_pool); + } + + return merge_err; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +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_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; + svn_boolean_t has_explicit_target = FALSE; + + /* Merge doesn't support specifying a revision or revision range + when using --reintegrate. */ + 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)); + + /* For now, we require at least one source. That may change in + future versions of Subversion, for example if we have support for + negated mergeinfo. See this IRC conversation: + + <bhuvan> kfogel: yeah, i think you are correct; we should + specify the source url + + <kfogel> bhuvan: I'll change the help output and propose for + backport. Thanks. + + <bhuvan> kfogel: np; while we are at it, 'svn merge' simply + returns nothing; i think we should say: """svn: Not + enough arguments provided; try 'svn help' for more + info""" + + <kfogel> good idea + + <kfogel> (in the future, 'svn merge' might actually do + something, but that's all the more reason to make + sure it errors now) + + <cmpilato> actually, i'm pretty sure 'svn merge' does something + + <cmpilato> it says "please merge any unmerged changes from + myself to myself." + + <cmpilato> :-) + + <kfogel> har har + + <cmpilato> kfogel: i was serious. + + <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there + ever a reason for a user to run it? + + <cmpilato> kfogel: not while we don't have support for negated + mergeinfo. + + <kfogel> cmpilato: do you concur that until it does something + useful it should error? + + <cmpilato> kfogel: yup. + + <kfogel> cool + */ + if (targets->nelts < 1) + { + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, + _("Merge source required")); + } + else /* Parse at least one, and possible two, sources. */ + { + 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)); + } + + /* We could have one or two sources. Deliberately written to stay + correct even if we someday permit implied merge source. */ + 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 revision_ranges has at least one real range at this point, then + we know the user must have used the '-r' and/or '-c' switch(es). + This means we're *not* doing two distinct sources. */ + if (first_range_start.kind != svn_opt_revision_unspecified) + { + /* A revision *range* is required. */ + 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) /* TODO: Switch order of if */ + { + if (targets->nelts > 2) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Too many arguments given")); + + /* Set the default value for unspecified paths and peg revision. */ + /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge + SOURCE WCPATH") here. */ + 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 /* using @rev syntax */ + { + 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; + + /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit + revisions--since it ignores local modifications it may not do what + the user expects. That is, it doesn't read from the WC itself, it + reads from the WC's URL. Forcing the user to specify a repository + revision should avoid any confusion. */ + 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)); + + /* Default peg revisions to each URL's youngest revision. */ + 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; + + /* Decide where to apply the delta (defaulting to "."). */ + if (targets->nelts == 3) + { + targetpath = APR_ARRAY_IDX(targets, 2, const char *); + has_explicit_target = TRUE; + } + } + + /* If no targetpath was specified, see if we can infer it from the + sourcepaths. */ + if (! has_explicit_target + && sourcepath1 && sourcepath2 + && strcmp(targetpath, "") == 0) + { + /* If the sourcepath is a URL, it can only refer to a target in + the current working directory or which is the current working + directory. However, if the sourcepath is a local path, it can + refer to a target somewhere deeper in the directory structure. */ + 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 the basename of the source is the same as the basename of + the cwd assume the cwd is the target. */ + if (strcmp(sp1_basename, target_base) != 0) + { + svn_node_kind_t kind; + + /* If the basename of the source differs from the basename + of the target. We still might assume the cwd is the + target, but first check if there is a file in the cwd + with the same name as the source basename. If there is, + then assume that file is the target. */ + 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; + + /* More input validation. */ + 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")); + } + + 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'")); + } + + 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); +} |