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