merge-cmd.c revision 299742
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 (opt_state->verbose) 127 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n"))); 128 merge_err = svn_client_merge5(sourcepath1, 129 &first_range_start, 130 sourcepath2, 131 &first_range_end, 132 targetpath, 133 opt_state->depth, 134 opt_state->ignore_ancestry, 135 opt_state->ignore_ancestry, 136 opt_state->force, /* force_delete */ 137 opt_state->record_only, 138 opt_state->dry_run, 139 opt_state->allow_mixed_rev, 140 options, 141 ctx, 142 scratch_pool); 143 } 144 145 return merge_err; 146} 147 148/* This implements the `svn_opt_subcommand_t' interface. */ 149svn_error_t * 150svn_cl__merge(apr_getopt_t *os, 151 void *baton, 152 apr_pool_t *pool) 153{ 154 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 155 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 156 apr_array_header_t *targets; 157 const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = ""; 158 svn_boolean_t two_sources_specified = TRUE; 159 svn_error_t *merge_err; 160 svn_opt_revision_t first_range_start, first_range_end, peg_revision1, 161 peg_revision2; 162 apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges; 163 svn_boolean_t has_explicit_target = FALSE; 164 165 /* Merge doesn't support specifying a revision or revision range 166 when using --reintegrate. */ 167 if (opt_state->reintegrate 168 && opt_state->start_revision.kind != svn_opt_revision_unspecified) 169 { 170 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 171 _("-r and -c can't be used with --reintegrate")); 172 } 173 174 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 175 opt_state->targets, 176 ctx, FALSE, pool)); 177 178 /* For now, we require at least one source. That may change in 179 future versions of Subversion, for example if we have support for 180 negated mergeinfo. See this IRC conversation: 181 182 <bhuvan> kfogel: yeah, i think you are correct; we should 183 specify the source url 184 185 <kfogel> bhuvan: I'll change the help output and propose for 186 backport. Thanks. 187 188 <bhuvan> kfogel: np; while we are at it, 'svn merge' simply 189 returns nothing; i think we should say: """svn: Not 190 enough arguments provided; try 'svn help' for more 191 info""" 192 193 <kfogel> good idea 194 195 <kfogel> (in the future, 'svn merge' might actually do 196 something, but that's all the more reason to make 197 sure it errors now) 198 199 <cmpilato> actually, i'm pretty sure 'svn merge' does something 200 201 <cmpilato> it says "please merge any unmerged changes from 202 myself to myself." 203 204 <cmpilato> :-) 205 206 <kfogel> har har 207 208 <cmpilato> kfogel: i was serious. 209 210 <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there 211 ever a reason for a user to run it? 212 213 <cmpilato> kfogel: not while we don't have support for negated 214 mergeinfo. 215 216 <kfogel> cmpilato: do you concur that until it does something 217 useful it should error? 218 219 <cmpilato> kfogel: yup. 220 221 <kfogel> cool 222 */ 223 if (targets->nelts < 1) 224 { 225 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, 226 _("Merge source required")); 227 } 228 else /* Parse at least one, and possible two, sources. */ 229 { 230 SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1, 231 APR_ARRAY_IDX(targets, 0, const char *), 232 pool)); 233 if (targets->nelts >= 2) 234 SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2, 235 APR_ARRAY_IDX(targets, 1, const char *), 236 pool)); 237 } 238 239 /* We could have one or two sources. Deliberately written to stay 240 correct even if we someday permit implied merge source. */ 241 if (targets->nelts <= 1) 242 { 243 two_sources_specified = FALSE; 244 } 245 else if (targets->nelts == 2) 246 { 247 if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2)) 248 two_sources_specified = FALSE; 249 } 250 251 if (opt_state->revision_ranges->nelts > 0) 252 { 253 first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0, 254 svn_opt_revision_range_t *)->start; 255 first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0, 256 svn_opt_revision_range_t *)->end; 257 } 258 else 259 { 260 first_range_start.kind = first_range_end.kind = 261 svn_opt_revision_unspecified; 262 } 263 264 /* If revision_ranges has at least one real range at this point, then 265 we know the user must have used the '-r' and/or '-c' switch(es). 266 This means we're *not* doing two distinct sources. */ 267 if (first_range_start.kind != svn_opt_revision_unspecified) 268 { 269 /* A revision *range* is required. */ 270 if (first_range_end.kind == svn_opt_revision_unspecified) 271 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, 272 _("Second revision required")); 273 274 two_sources_specified = FALSE; 275 } 276 277 if (! two_sources_specified) /* TODO: Switch order of if */ 278 { 279 if (targets->nelts > 2) 280 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 281 _("Too many arguments given")); 282 283 /* Set the default value for unspecified paths and peg revision. */ 284 /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge 285 SOURCE WCPATH") here. */ 286 sourcepath2 = sourcepath1; 287 288 if (peg_revision1.kind == svn_opt_revision_unspecified) 289 peg_revision1.kind = svn_path_is_url(sourcepath1) 290 ? svn_opt_revision_head : svn_opt_revision_working; 291 292 if (targets->nelts == 2) 293 { 294 targetpath = APR_ARRAY_IDX(targets, 1, const char *); 295 has_explicit_target = TRUE; 296 if (svn_path_is_url(targetpath)) 297 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 298 _("Cannot specify a revision range " 299 "with two URLs")); 300 } 301 } 302 else /* using @rev syntax */ 303 { 304 if (targets->nelts < 2) 305 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); 306 if (targets->nelts > 3) 307 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 308 _("Too many arguments given")); 309 310 first_range_start = peg_revision1; 311 first_range_end = peg_revision2; 312 313 /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit 314 revisions--since it ignores local modifications it may not do what 315 the user expects. That is, it doesn't read from the WC itself, it 316 reads from the WC's URL. Forcing the user to specify a repository 317 revision should avoid any confusion. */ 318 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start, 319 pool)); 320 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end, 321 pool)); 322 323 /* Default peg revisions to each URL's youngest revision. */ 324 if (first_range_start.kind == svn_opt_revision_unspecified) 325 first_range_start.kind = svn_opt_revision_head; 326 if (first_range_end.kind == svn_opt_revision_unspecified) 327 first_range_end.kind = svn_opt_revision_head; 328 329 /* Decide where to apply the delta (defaulting to "."). */ 330 if (targets->nelts == 3) 331 { 332 targetpath = APR_ARRAY_IDX(targets, 2, const char *); 333 has_explicit_target = TRUE; 334 } 335 } 336 337 /* If no targetpath was specified, see if we can infer it from the 338 sourcepaths. */ 339 if (! has_explicit_target 340 && sourcepath1 && sourcepath2 341 && strcmp(targetpath, "") == 0) 342 { 343 /* If the sourcepath is a URL, it can only refer to a target in 344 the current working directory or which is the current working 345 directory. However, if the sourcepath is a local path, it can 346 refer to a target somewhere deeper in the directory structure. */ 347 if (svn_path_is_url(sourcepath1)) 348 { 349 const char *sp1_basename = svn_uri_basename(sourcepath1, pool); 350 const char *sp2_basename = svn_uri_basename(sourcepath2, pool); 351 352 if (strcmp(sp1_basename, sp2_basename) == 0) 353 { 354 const char *target_url; 355 const char *target_base; 356 357 SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx, 358 pool, pool)); 359 target_base = svn_uri_basename(target_url, pool); 360 361 /* If the basename of the source is the same as the basename of 362 the cwd assume the cwd is the target. */ 363 if (strcmp(sp1_basename, target_base) != 0) 364 { 365 svn_node_kind_t kind; 366 367 /* If the basename of the source differs from the basename 368 of the target. We still might assume the cwd is the 369 target, but first check if there is a file in the cwd 370 with the same name as the source basename. If there is, 371 then assume that file is the target. */ 372 SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool)); 373 if (kind == svn_node_file) 374 { 375 targetpath = sp1_basename; 376 } 377 } 378 } 379 } 380 else if (strcmp(sourcepath1, sourcepath2) == 0) 381 { 382 svn_node_kind_t kind; 383 384 SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool)); 385 if (kind == svn_node_file) 386 { 387 targetpath = sourcepath1; 388 } 389 } 390 } 391 392 if (opt_state->extensions) 393 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); 394 else 395 options = NULL; 396 397 /* More input validation. */ 398 if (opt_state->reintegrate) 399 { 400 if (opt_state->ignore_ancestry) 401 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 402 _("--reintegrate cannot be used with " 403 "--ignore-ancestry")); 404 405 if (opt_state->record_only) 406 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 407 _("--reintegrate cannot be used with " 408 "--record-only")); 409 410 if (opt_state->depth != svn_depth_unknown) 411 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 412 _("--depth cannot be used with " 413 "--reintegrate")); 414 415 if (opt_state->force) 416 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 417 _("--force cannot be used with " 418 "--reintegrate")); 419 420 if (two_sources_specified) 421 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 422 _("--reintegrate can only be used with " 423 "a single merge source")); 424 if (opt_state->allow_mixed_rev) 425 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 426 _("--allow-mixed-revisions cannot be used " 427 "with --reintegrate")); 428 } 429 430 merge_err = run_merge(two_sources_specified, 431 sourcepath1, peg_revision1, 432 sourcepath2, 433 targetpath, 434 ranges_to_merge, first_range_start, first_range_end, 435 opt_state, options, ctx, pool); 436 if (merge_err && merge_err->apr_err 437 == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING) 438 { 439 return svn_error_quick_wrap( 440 merge_err, 441 _("Merge tracking not possible, use --ignore-ancestry or\n" 442 "fix invalid mergeinfo in target with 'svn propset'")); 443 } 444 445 if (!opt_state->quiet) 446 { 447 svn_error_t *err = svn_cl__notifier_print_conflict_stats( 448 ctx->notify_baton2, pool); 449 450 merge_err = svn_error_compose_create(merge_err, err); 451 } 452 453 return svn_cl__may_need_force(merge_err); 454} 455