2 * merge-cmd.c -- Merging changes into a working copy.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_client.h"
31 #include "svn_dirent_uri.h"
33 #include "svn_error.h"
34 #include "svn_types.h"
36 #include "private/svn_client_private.h"
38 #include "svn_private_config.h"
40 /* A handy constant */
41 static const svn_opt_revision_t unspecified_revision
42 = { svn_opt_revision_unspecified, { 0 } };
47 /* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
50 ensure_wc_path_has_repo_revision(const char *path_or_url,
51 const svn_opt_revision_t *revision,
52 apr_pool_t *scratch_pool)
54 if (revision->kind != svn_opt_revision_number
55 && revision->kind != svn_opt_revision_date
56 && revision->kind != svn_opt_revision_head
57 && ! svn_path_is_url(path_or_url))
58 return svn_error_createf(
59 SVN_ERR_CLIENT_BAD_REVISION, NULL,
60 _("Invalid merge source '%s'; a working copy path can only be "
61 "used with a repository revision (a number, a date, or head)"),
62 svn_dirent_local_style(path_or_url, scratch_pool));
68 * (No docs yet, as this code was just hoisted out of svn_cl__merge().)
70 * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use
71 * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller.
74 run_merge(svn_boolean_t two_sources_specified,
75 const char *sourcepath1,
76 svn_opt_revision_t peg_revision1,
77 const char *sourcepath2,
78 const char *targetpath,
79 apr_array_header_t *ranges_to_merge,
80 svn_opt_revision_t first_range_start,
81 svn_opt_revision_t first_range_end,
82 svn_cl__opt_state_t *opt_state,
83 apr_array_header_t *options,
84 svn_client_ctx_t *ctx,
85 apr_pool_t *scratch_pool)
87 svn_error_t *merge_err;
89 if (opt_state->reintegrate)
91 merge_err = svn_cl__deprecated_merge_reintegrate(
92 sourcepath1, &peg_revision1, targetpath,
93 opt_state->dry_run, options, ctx, scratch_pool);
95 else if (! two_sources_specified)
97 /* If we don't have at least one valid revision range, pick a
98 good one that spans the entire set of revisions on our
100 if ((first_range_start.kind == svn_opt_revision_unspecified)
101 && (first_range_end.kind == svn_opt_revision_unspecified))
103 ranges_to_merge = NULL;
106 if (opt_state->verbose)
107 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
108 merge_err = svn_client_merge_peg5(sourcepath1,
113 opt_state->ignore_ancestry,
114 opt_state->ignore_ancestry,
115 opt_state->force, /* force_delete */
116 opt_state->record_only,
118 opt_state->allow_mixed_rev,
125 if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2))
126 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
127 _("Merge sources must both be "
128 "either paths or URLs"));
130 if (opt_state->verbose)
131 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
132 merge_err = svn_client_merge5(sourcepath1,
138 opt_state->ignore_ancestry,
139 opt_state->ignore_ancestry,
140 opt_state->force, /* force_delete */
141 opt_state->record_only,
143 opt_state->allow_mixed_rev,
152 /* This implements the `svn_opt_subcommand_t' interface. */
154 svn_cl__merge(apr_getopt_t *os,
158 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
159 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
160 apr_array_header_t *targets;
161 const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = "";
162 svn_boolean_t two_sources_specified = TRUE;
163 svn_error_t *merge_err;
164 svn_opt_revision_t first_range_start, first_range_end, peg_revision1,
166 apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
167 svn_boolean_t has_explicit_target = FALSE;
169 /* Merge doesn't support specifying a revision or revision range
170 when using --reintegrate. */
171 if (opt_state->reintegrate
172 && opt_state->start_revision.kind != svn_opt_revision_unspecified)
174 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
175 _("-r and -c can't be used with --reintegrate"));
178 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
182 /* For now, we require at least one source. That may change in
183 future versions of Subversion, for example if we have support for
184 negated mergeinfo. See this IRC conversation:
186 <bhuvan> kfogel: yeah, i think you are correct; we should
187 specify the source url
189 <kfogel> bhuvan: I'll change the help output and propose for
192 <bhuvan> kfogel: np; while we are at it, 'svn merge' simply
193 returns nothing; i think we should say: """svn: Not
194 enough arguments provided; try 'svn help' for more
199 <kfogel> (in the future, 'svn merge' might actually do
200 something, but that's all the more reason to make
203 <cmpilato> actually, i'm pretty sure 'svn merge' does something
205 <cmpilato> it says "please merge any unmerged changes from
212 <cmpilato> kfogel: i was serious.
214 <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there
215 ever a reason for a user to run it?
217 <cmpilato> kfogel: not while we don't have support for negated
220 <kfogel> cmpilato: do you concur that until it does something
221 useful it should error?
223 <cmpilato> kfogel: yup.
227 if (targets->nelts < 1)
229 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
230 _("Merge source required"));
232 else /* Parse at least one, and possible two, sources. */
234 SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
235 APR_ARRAY_IDX(targets, 0, const char *),
237 if (targets->nelts >= 2)
238 SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
239 APR_ARRAY_IDX(targets, 1, const char *),
243 /* We could have one or two sources. Deliberately written to stay
244 correct even if we someday permit implied merge source. */
245 if (targets->nelts <= 1)
247 two_sources_specified = FALSE;
249 else if (targets->nelts == 2)
251 if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
252 two_sources_specified = FALSE;
255 if (opt_state->revision_ranges->nelts > 0)
257 first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
258 svn_opt_revision_range_t *)->start;
259 first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
260 svn_opt_revision_range_t *)->end;
264 first_range_start.kind = first_range_end.kind =
265 svn_opt_revision_unspecified;
268 /* If revision_ranges has at least one real range at this point, then
269 we know the user must have used the '-r' and/or '-c' switch(es).
270 This means we're *not* doing two distinct sources. */
271 if (first_range_start.kind != svn_opt_revision_unspecified)
273 /* A revision *range* is required. */
274 if (first_range_end.kind == svn_opt_revision_unspecified)
275 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
276 _("Second revision required"));
278 two_sources_specified = FALSE;
281 if (! two_sources_specified) /* TODO: Switch order of if */
283 if (targets->nelts > 2)
284 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
285 _("Too many arguments given"));
287 /* Set the default value for unspecified paths and peg revision. */
288 /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge
289 SOURCE WCPATH") here. */
290 sourcepath2 = sourcepath1;
292 if (peg_revision1.kind == svn_opt_revision_unspecified)
293 peg_revision1.kind = svn_path_is_url(sourcepath1)
294 ? svn_opt_revision_head : svn_opt_revision_working;
296 if (targets->nelts == 2)
298 targetpath = APR_ARRAY_IDX(targets, 1, const char *);
299 has_explicit_target = TRUE;
300 if (svn_path_is_url(targetpath))
301 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
302 _("Cannot specify a revision range "
306 else /* using @rev syntax */
308 if (targets->nelts < 2)
309 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
310 if (targets->nelts > 3)
311 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
312 _("Too many arguments given"));
314 first_range_start = peg_revision1;
315 first_range_end = peg_revision2;
317 /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit
318 revisions--since it ignores local modifications it may not do what
319 the user expects. That is, it doesn't read from the WC itself, it
320 reads from the WC's URL. Forcing the user to specify a repository
321 revision should avoid any confusion. */
322 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start,
324 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
327 /* Default peg revisions to each URL's youngest revision. */
328 if (first_range_start.kind == svn_opt_revision_unspecified)
329 first_range_start.kind = svn_opt_revision_head;
330 if (first_range_end.kind == svn_opt_revision_unspecified)
331 first_range_end.kind = svn_opt_revision_head;
333 /* Decide where to apply the delta (defaulting to "."). */
334 if (targets->nelts == 3)
336 targetpath = APR_ARRAY_IDX(targets, 2, const char *);
337 has_explicit_target = TRUE;
341 /* If no targetpath was specified, see if we can infer it from the
343 if (! has_explicit_target
344 && sourcepath1 && sourcepath2
345 && strcmp(targetpath, "") == 0)
347 /* If the sourcepath is a URL, it can only refer to a target in
348 the current working directory or which is the current working
349 directory. However, if the sourcepath is a local path, it can
350 refer to a target somewhere deeper in the directory structure. */
351 if (svn_path_is_url(sourcepath1))
353 const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
354 const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
356 if (strcmp(sp1_basename, sp2_basename) == 0)
358 const char *target_url;
359 const char *target_base;
361 SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
363 target_base = svn_uri_basename(target_url, pool);
365 /* If the basename of the source is the same as the basename of
366 the cwd assume the cwd is the target. */
367 if (strcmp(sp1_basename, target_base) != 0)
369 svn_node_kind_t kind;
371 /* If the basename of the source differs from the basename
372 of the target. We still might assume the cwd is the
373 target, but first check if there is a file in the cwd
374 with the same name as the source basename. If there is,
375 then assume that file is the target. */
376 SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool));
377 if (kind == svn_node_file)
379 targetpath = sp1_basename;
384 else if (strcmp(sourcepath1, sourcepath2) == 0)
386 svn_node_kind_t kind;
388 SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
389 if (kind == svn_node_file)
391 targetpath = sourcepath1;
396 if (opt_state->extensions)
397 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
401 /* More input validation. */
402 if (opt_state->reintegrate)
404 if (opt_state->ignore_ancestry)
405 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
406 _("--reintegrate cannot be used with "
407 "--ignore-ancestry"));
409 if (opt_state->record_only)
410 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
411 _("--reintegrate cannot be used with "
414 if (opt_state->depth != svn_depth_unknown)
415 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
416 _("--depth cannot be used with "
419 if (opt_state->force)
420 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
421 _("--force cannot be used with "
424 if (two_sources_specified)
425 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
426 _("--reintegrate can only be used with "
427 "a single merge source"));
428 if (opt_state->allow_mixed_rev)
429 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
430 _("--allow-mixed-revisions cannot be used "
431 "with --reintegrate"));
434 merge_err = run_merge(two_sources_specified,
435 sourcepath1, peg_revision1,
438 ranges_to_merge, first_range_start, first_range_end,
439 opt_state, options, ctx, pool);
440 if (merge_err && merge_err->apr_err
441 == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
443 return svn_error_quick_wrap(
445 _("Merge tracking not possible, use --ignore-ancestry or\n"
446 "fix invalid mergeinfo in target with 'svn propset'"));
449 if (!opt_state->quiet)
451 svn_error_t *err = svn_cl__notifier_print_conflict_stats(
452 ctx->notify_baton2, pool);
454 merge_err = svn_error_compose_create(merge_err, err);
457 return svn_cl__may_need_force(merge_err);