merge-cmd.c revision 309512
1/* 2 * merge-cmd.c -- Merging changes into a working copy. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_client.h" 31#include "svn_dirent_uri.h" 32#include "svn_path.h" 33#include "svn_error.h" 34#include "svn_types.h" 35#include "cl.h" 36#include "private/svn_client_private.h" 37 38#include "svn_private_config.h" 39 40 41/*** Code. ***/ 42 43/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository 44 * revision. */ 45static svn_error_t * 46ensure_wc_path_has_repo_revision(const char *path_or_url, 47 const svn_opt_revision_t *revision, 48 apr_pool_t *scratch_pool) 49{ 50 if (revision->kind != svn_opt_revision_number 51 && revision->kind != svn_opt_revision_date 52 && revision->kind != svn_opt_revision_head 53 && ! svn_path_is_url(path_or_url)) 54 return svn_error_createf( 55 SVN_ERR_CLIENT_BAD_REVISION, NULL, 56 _("Invalid merge source '%s'; a working copy path can only be " 57 "used with a repository revision (a number, a date, or head)"), 58 svn_dirent_local_style(path_or_url, scratch_pool)); 59 return SVN_NO_ERROR; 60} 61 62/* Run a merge. 63 * 64 * (No docs yet, as this code was just hoisted out of svn_cl__merge().) 65 * 66 * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use 67 * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller. 68 */ 69static svn_error_t * 70run_merge(svn_boolean_t two_sources_specified, 71 const char *sourcepath1, 72 svn_opt_revision_t peg_revision1, 73 const char *sourcepath2, 74 const char *targetpath, 75 apr_array_header_t *ranges_to_merge, 76 svn_opt_revision_t first_range_start, 77 svn_opt_revision_t first_range_end, 78 svn_cl__opt_state_t *opt_state, 79 apr_array_header_t *options, 80 svn_client_ctx_t *ctx, 81 apr_pool_t *scratch_pool) 82{ 83 svn_error_t *merge_err; 84 85 if (opt_state->reintegrate) 86 { 87 merge_err = svn_cl__deprecated_merge_reintegrate( 88 sourcepath1, &peg_revision1, targetpath, 89 opt_state->dry_run, options, ctx, scratch_pool); 90 } 91 else if (! two_sources_specified) 92 { 93 /* If we don't have at least one valid revision range, pick a 94 good one that spans the entire set of revisions on our 95 source. */ 96 if ((first_range_start.kind == svn_opt_revision_unspecified) 97 && (first_range_end.kind == svn_opt_revision_unspecified)) 98 { 99 ranges_to_merge = NULL; 100 } 101 102 if (opt_state->verbose) 103 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n"))); 104 merge_err = svn_client_merge_peg5(sourcepath1, 105 ranges_to_merge, 106 &peg_revision1, 107 targetpath, 108 opt_state->depth, 109 opt_state->ignore_ancestry, 110 opt_state->ignore_ancestry, 111 opt_state->force, /* force_delete */ 112 opt_state->record_only, 113 opt_state->dry_run, 114 opt_state->allow_mixed_rev, 115 options, 116 ctx, 117 scratch_pool); 118 } 119 else 120 { 121 if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2)) 122 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 123 _("Merge sources must both be " 124 "either paths or URLs")); 125 126 if (svn_path_is_url(targetpath)) 127 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 128 _("Merge target '%s' must be a local path " 129 "but looks like a URL"), targetpath); 130 131 if (opt_state->verbose) 132 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n"))); 133 merge_err = svn_client_merge5(sourcepath1, 134 &first_range_start, 135 sourcepath2, 136 &first_range_end, 137 targetpath, 138 opt_state->depth, 139 opt_state->ignore_ancestry, 140 opt_state->ignore_ancestry, 141 opt_state->force, /* force_delete */ 142 opt_state->record_only, 143 opt_state->dry_run, 144 opt_state->allow_mixed_rev, 145 options, 146 ctx, 147 scratch_pool); 148 } 149 150 return merge_err; 151} 152 153/* This implements the `svn_opt_subcommand_t' interface. */ 154svn_error_t * 155svn_cl__merge(apr_getopt_t *os, 156 void *baton, 157 apr_pool_t *pool) 158{ 159 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 160 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 161 apr_array_header_t *targets; 162 const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = ""; 163 svn_boolean_t two_sources_specified = TRUE; 164 svn_error_t *merge_err; 165 svn_opt_revision_t first_range_start, first_range_end, peg_revision1, 166 peg_revision2; 167 apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges; 168 svn_boolean_t has_explicit_target = FALSE; 169 170 /* Merge doesn't support specifying a revision or revision range 171 when using --reintegrate. */ 172 if (opt_state->reintegrate 173 && opt_state->start_revision.kind != svn_opt_revision_unspecified) 174 { 175 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 176 _("-r and -c can't be used with --reintegrate")); 177 } 178 179 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 180 opt_state->targets, 181 ctx, FALSE, pool)); 182 183 /* For now, we require at least one source. That may change in 184 future versions of Subversion, for example if we have support for 185 negated mergeinfo. See this IRC conversation: 186 187 <bhuvan> kfogel: yeah, i think you are correct; we should 188 specify the source url 189 190 <kfogel> bhuvan: I'll change the help output and propose for 191 backport. Thanks. 192 193 <bhuvan> kfogel: np; while we are at it, 'svn merge' simply 194 returns nothing; i think we should say: """svn: Not 195 enough arguments provided; try 'svn help' for more 196 info""" 197 198 <kfogel> good idea 199 200 <kfogel> (in the future, 'svn merge' might actually do 201 something, but that's all the more reason to make 202 sure it errors now) 203 204 <cmpilato> actually, i'm pretty sure 'svn merge' does something 205 206 <cmpilato> it says "please merge any unmerged changes from 207 myself to myself." 208 209 <cmpilato> :-) 210 211 <kfogel> har har 212 213 <cmpilato> kfogel: i was serious. 214 215 <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there 216 ever a reason for a user to run it? 217 218 <cmpilato> kfogel: not while we don't have support for negated 219 mergeinfo. 220 221 <kfogel> cmpilato: do you concur that until it does something 222 useful it should error? 223 224 <cmpilato> kfogel: yup. 225 226 <kfogel> cool 227 */ 228 if (targets->nelts < 1) 229 { 230 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, 231 _("Merge source required")); 232 } 233 else /* Parse at least one, and possible two, sources. */ 234 { 235 SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1, 236 APR_ARRAY_IDX(targets, 0, const char *), 237 pool)); 238 if (targets->nelts >= 2) 239 SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2, 240 APR_ARRAY_IDX(targets, 1, const char *), 241 pool)); 242 } 243 244 /* We could have one or two sources. Deliberately written to stay 245 correct even if we someday permit implied merge source. */ 246 if (targets->nelts <= 1) 247 { 248 two_sources_specified = FALSE; 249 } 250 else if (targets->nelts == 2) 251 { 252 if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2)) 253 two_sources_specified = FALSE; 254 } 255 256 if (opt_state->revision_ranges->nelts > 0) 257 { 258 first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0, 259 svn_opt_revision_range_t *)->start; 260 first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0, 261 svn_opt_revision_range_t *)->end; 262 } 263 else 264 { 265 first_range_start.kind = first_range_end.kind = 266 svn_opt_revision_unspecified; 267 } 268 269 /* If revision_ranges has at least one real range at this point, then 270 we know the user must have used the '-r' and/or '-c' switch(es). 271 This means we're *not* doing two distinct sources. */ 272 if (first_range_start.kind != svn_opt_revision_unspecified) 273 { 274 /* A revision *range* is required. */ 275 if (first_range_end.kind == svn_opt_revision_unspecified) 276 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, 277 _("Second revision required")); 278 279 two_sources_specified = FALSE; 280 } 281 282 if (! two_sources_specified) /* TODO: Switch order of if */ 283 { 284 if (targets->nelts > 2) 285 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 286 _("Too many arguments given")); 287 288 /* Set the default value for unspecified paths and peg revision. */ 289 /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge 290 SOURCE WCPATH") here. */ 291 sourcepath2 = sourcepath1; 292 293 if (peg_revision1.kind == svn_opt_revision_unspecified) 294 peg_revision1.kind = svn_path_is_url(sourcepath1) 295 ? svn_opt_revision_head : svn_opt_revision_working; 296 297 if (targets->nelts == 2) 298 { 299 targetpath = APR_ARRAY_IDX(targets, 1, const char *); 300 has_explicit_target = TRUE; 301 if (svn_path_is_url(targetpath)) 302 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 303 _("Cannot specify a revision range " 304 "with two URLs")); 305 } 306 } 307 else /* using @rev syntax */ 308 { 309 if (targets->nelts < 2) 310 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); 311 if (targets->nelts > 3) 312 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 313 _("Too many arguments given")); 314 315 first_range_start = peg_revision1; 316 first_range_end = peg_revision2; 317 318 /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit 319 revisions--since it ignores local modifications it may not do what 320 the user expects. That is, it doesn't read from the WC itself, it 321 reads from the WC's URL. Forcing the user to specify a repository 322 revision should avoid any confusion. */ 323 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start, 324 pool)); 325 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end, 326 pool)); 327 328 /* Default peg revisions to each URL's youngest revision. */ 329 if (first_range_start.kind == svn_opt_revision_unspecified) 330 first_range_start.kind = svn_opt_revision_head; 331 if (first_range_end.kind == svn_opt_revision_unspecified) 332 first_range_end.kind = svn_opt_revision_head; 333 334 /* Decide where to apply the delta (defaulting to "."). */ 335 if (targets->nelts == 3) 336 { 337 targetpath = APR_ARRAY_IDX(targets, 2, const char *); 338 has_explicit_target = TRUE; 339 } 340 } 341 342 /* If no targetpath was specified, see if we can infer it from the 343 sourcepaths. */ 344 if (! has_explicit_target 345 && sourcepath1 && sourcepath2 346 && strcmp(targetpath, "") == 0) 347 { 348 /* If the sourcepath is a URL, it can only refer to a target in 349 the current working directory or which is the current working 350 directory. However, if the sourcepath is a local path, it can 351 refer to a target somewhere deeper in the directory structure. */ 352 if (svn_path_is_url(sourcepath1)) 353 { 354 const char *sp1_basename = svn_uri_basename(sourcepath1, pool); 355 const char *sp2_basename = svn_uri_basename(sourcepath2, pool); 356 357 if (strcmp(sp1_basename, sp2_basename) == 0) 358 { 359 const char *target_url; 360 const char *target_base; 361 362 SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx, 363 pool, pool)); 364 target_base = svn_uri_basename(target_url, pool); 365 366 /* If the basename of the source is the same as the basename of 367 the cwd assume the cwd is the target. */ 368 if (strcmp(sp1_basename, target_base) != 0) 369 { 370 svn_node_kind_t kind; 371 372 /* If the basename of the source differs from the basename 373 of the target. We still might assume the cwd is the 374 target, but first check if there is a file in the cwd 375 with the same name as the source basename. If there is, 376 then assume that file is the target. */ 377 SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool)); 378 if (kind == svn_node_file) 379 { 380 targetpath = sp1_basename; 381 } 382 } 383 } 384 } 385 else if (strcmp(sourcepath1, sourcepath2) == 0) 386 { 387 svn_node_kind_t kind; 388 389 SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool)); 390 if (kind == svn_node_file) 391 { 392 targetpath = sourcepath1; 393 } 394 } 395 } 396 397 if (opt_state->extensions) 398 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); 399 else 400 options = NULL; 401 402 /* More input validation. */ 403 if (opt_state->reintegrate) 404 { 405 if (opt_state->ignore_ancestry) 406 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 407 _("--reintegrate cannot be used with " 408 "--ignore-ancestry")); 409 410 if (opt_state->record_only) 411 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 412 _("--reintegrate cannot be used with " 413 "--record-only")); 414 415 if (opt_state->depth != svn_depth_unknown) 416 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 417 _("--depth cannot be used with " 418 "--reintegrate")); 419 420 if (opt_state->force) 421 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 422 _("--force cannot be used with " 423 "--reintegrate")); 424 425 if (two_sources_specified) 426 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 427 _("--reintegrate can only be used with " 428 "a single merge source")); 429 if (opt_state->allow_mixed_rev) 430 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 431 _("--allow-mixed-revisions cannot be used " 432 "with --reintegrate")); 433 } 434 435 merge_err = run_merge(two_sources_specified, 436 sourcepath1, peg_revision1, 437 sourcepath2, 438 targetpath, 439 ranges_to_merge, first_range_start, first_range_end, 440 opt_state, options, ctx, pool); 441 if (merge_err && merge_err->apr_err 442 == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING) 443 { 444 return svn_error_quick_wrap( 445 merge_err, 446 _("Merge tracking not possible, use --ignore-ancestry or\n" 447 "fix invalid mergeinfo in target with 'svn propset'")); 448 } 449 450 if (!opt_state->quiet) 451 { 452 svn_error_t *err = svn_cl__notifier_print_conflict_stats( 453 ctx->notify_baton2, pool); 454 455 merge_err = svn_error_compose_create(merge_err, err); 456 } 457 458 return svn_cl__may_need_force(merge_err); 459} 460