| /* |
| * 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 "svn_private_config.h" |
| |
| |
| /*** Code. ***/ |
| |
| |
| /* 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 *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; |
| |
| /* 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, 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. */ |
| if (targets->nelts == 0) |
| { |
| peg_revision1.kind = svn_opt_revision_head; |
| } |
| else |
| { |
| /* 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 *); |
| 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. Forcing the user to specify a repository |
| revision should avoid any confusion. */ |
| if ((first_range_start.kind == svn_opt_revision_unspecified |
| && ! svn_path_is_url(sourcepath1)) |
| || |
| (first_range_end.kind == svn_opt_revision_unspecified |
| && ! svn_path_is_url(sourcepath2))) |
| return svn_error_create |
| (SVN_ERR_CLIENT_BAD_REVISION, 0, |
| _("A working copy merge source needs an explicit revision")); |
| |
| /* 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 *); |
| } |
| |
| /* If no targetpath was specified, see if we can infer it from the |
| sourcepaths. */ |
| if (sourcepath1 && sourcepath2 && strcmp(targetpath, "") == 0) |
| { |
| /* If the sourcepath is a URL, it can only refer to a target in |
| 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) |
| { |
| 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; |
| const char *decoded_path = svn_path_uri_decode(sourcepath1, pool); |
| SVN_ERR(svn_io_check_path(decoded_path, &kind, pool)); |
| if (kind == svn_node_file) |
| { |
| targetpath = decoded_path; |
| } |
| } |
| } |
| |
| 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->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 (! two_sources_specified) /* TODO: Switch order of if */ |
| { |
| /* If we don't have a source, use the target as the source. */ |
| if (! sourcepath1) |
| sourcepath1 = targetpath; |
| |
| /* 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)) |
| { |
| svn_opt_revision_range_t *range = apr_pcalloc(pool, sizeof(*range)); |
| ranges_to_merge = apr_array_make(pool, 1, sizeof(range)); |
| range->start.kind = svn_opt_revision_number; |
| range->start.value.number = 1; |
| range->end = peg_revision1; |
| APR_ARRAY_PUSH(ranges_to_merge, svn_opt_revision_range_t *) = range; |
| } |
| |
| if (opt_state->reintegrate) |
| err = svn_client_merge_reintegrate(sourcepath1, |
| &peg_revision1, |
| targetpath, |
| opt_state->dry_run, |
| options, ctx, pool); |
| else |
| err = svn_client_merge_peg4(sourcepath1, |
| ranges_to_merge, |
| &peg_revision1, |
| targetpath, |
| opt_state->depth, |
| opt_state->ignore_ancestry, |
| opt_state->force, |
| opt_state->record_only, |
| opt_state->dry_run, |
| opt_state->allow_mixed_rev, |
| options, |
| ctx, |
| pool); |
| } |
| else |
| { |
| if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2)) |
| return svn_error_return(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, |
| NULL, |
| _("Merge sources must both be " |
| "either paths or URLs"))); |
| err = svn_client_merge4(sourcepath1, |
| &first_range_start, |
| sourcepath2, |
| &first_range_end, |
| targetpath, |
| opt_state->depth, |
| opt_state->ignore_ancestry, |
| opt_state->force, |
| opt_state->record_only, |
| opt_state->dry_run, |
| opt_state->allow_mixed_rev, |
| options, |
| ctx, |
| pool); |
| } |
| |
| if (! opt_state->quiet) |
| SVN_ERR(svn_cl__print_conflict_stats(ctx->notify_baton2, pool)); |
| |
| if (err && (! opt_state->reintegrate)) |
| return svn_cl__may_need_force(err); |
| |
| return svn_error_return(err); |
| } |