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"
43 /* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
46 ensure_wc_path_has_repo_revision(const char *path_or_url,
47 const svn_opt_revision_t *revision,
48 apr_pool_t *scratch_pool)
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));
64 * (No docs yet, as this code was just hoisted out of svn_cl__merge().)
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.
70 run_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)
83 svn_error_t *merge_err;
85 if (opt_state->reintegrate)
87 merge_err = svn_cl__deprecated_merge_reintegrate(
88 sourcepath1, &peg_revision1, targetpath,
89 opt_state->dry_run, options, ctx, scratch_pool);
91 else if (! two_sources_specified)
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
96 if ((first_range_start.kind == svn_opt_revision_unspecified)
97 && (first_range_end.kind == svn_opt_revision_unspecified))
99 ranges_to_merge = NULL;
102 if (opt_state->verbose)
103 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
104 merge_err = svn_client_merge_peg5(sourcepath1,
109 opt_state->ignore_ancestry,
110 opt_state->ignore_ancestry,
111 opt_state->force, /* force_delete */
112 opt_state->record_only,
114 opt_state->allow_mixed_rev,
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"));
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);
131 if (opt_state->verbose)
132 SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
133 merge_err = svn_client_merge5(sourcepath1,
139 opt_state->ignore_ancestry,
140 opt_state->ignore_ancestry,
141 opt_state->force, /* force_delete */
142 opt_state->record_only,
144 opt_state->allow_mixed_rev,
153 /* This implements the `svn_opt_subcommand_t' interface. */
155 svn_cl__merge(apr_getopt_t *os,
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,
167 apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
168 svn_boolean_t has_explicit_target = FALSE;
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)
175 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
176 _("-r and -c can't be used with --reintegrate"));
179 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
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:
187 <bhuvan> kfogel: yeah, i think you are correct; we should
188 specify the source url
190 <kfogel> bhuvan: I'll change the help output and propose for
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
200 <kfogel> (in the future, 'svn merge' might actually do
201 something, but that's all the more reason to make
204 <cmpilato> actually, i'm pretty sure 'svn merge' does something
206 <cmpilato> it says "please merge any unmerged changes from
213 <cmpilato> kfogel: i was serious.
215 <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there
216 ever a reason for a user to run it?
218 <cmpilato> kfogel: not while we don't have support for negated
221 <kfogel> cmpilato: do you concur that until it does something
222 useful it should error?
224 <cmpilato> kfogel: yup.
228 if (targets->nelts < 1)
230 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
231 _("Merge source required"));
233 else /* Parse at least one, and possible two, sources. */
235 SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
236 APR_ARRAY_IDX(targets, 0, const char *),
238 if (targets->nelts >= 2)
239 SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
240 APR_ARRAY_IDX(targets, 1, const char *),
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)
248 two_sources_specified = FALSE;
250 else if (targets->nelts == 2)
252 if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
253 two_sources_specified = FALSE;
256 if (opt_state->revision_ranges->nelts > 0)
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;
265 first_range_start.kind = first_range_end.kind =
266 svn_opt_revision_unspecified;
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)
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"));
279 two_sources_specified = FALSE;
282 if (! two_sources_specified) /* TODO: Switch order of if */
284 if (targets->nelts > 2)
285 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
286 _("Too many arguments given"));
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;
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;
297 if (targets->nelts == 2)
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 "
307 else /* using @rev syntax */
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"));
315 first_range_start = peg_revision1;
316 first_range_end = peg_revision2;
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,
325 SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
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;
334 /* Decide where to apply the delta (defaulting to "."). */
335 if (targets->nelts == 3)
337 targetpath = APR_ARRAY_IDX(targets, 2, const char *);
338 has_explicit_target = TRUE;
342 /* If no targetpath was specified, see if we can infer it from the
344 if (! has_explicit_target
345 && sourcepath1 && sourcepath2
346 && strcmp(targetpath, "") == 0)
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))
354 const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
355 const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
357 if (strcmp(sp1_basename, sp2_basename) == 0)
359 const char *target_url;
360 const char *target_base;
362 SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
364 target_base = svn_uri_basename(target_url, pool);
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)
370 svn_node_kind_t kind;
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)
380 targetpath = sp1_basename;
385 else if (strcmp(sourcepath1, sourcepath2) == 0)
387 svn_node_kind_t kind;
389 SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
390 if (kind == svn_node_file)
392 targetpath = sourcepath1;
397 if (opt_state->extensions)
398 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
402 /* More input validation. */
403 if (opt_state->reintegrate)
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"));
410 if (opt_state->record_only)
411 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
412 _("--reintegrate cannot be used with "
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 "
420 if (opt_state->force)
421 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
422 _("--force cannot be used with "
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"));
435 merge_err = run_merge(two_sources_specified,
436 sourcepath1, peg_revision1,
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)
444 return svn_error_quick_wrap(
446 _("Merge tracking not possible, use --ignore-ancestry or\n"
447 "fix invalid mergeinfo in target with 'svn propset'"));
450 if (!opt_state->quiet)
452 svn_error_t *err = svn_cl__notifier_print_conflict_stats(
453 ctx->notify_baton2, pool);
455 merge_err = svn_error_compose_create(merge_err, err);
458 return svn_cl__may_need_force(merge_err);