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