1251881Speter/*
2251881Speter * merge-cmd.c -- Merging changes into a working copy.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include "svn_client.h"
31251881Speter#include "svn_dirent_uri.h"
32251881Speter#include "svn_path.h"
33251881Speter#include "svn_error.h"
34251881Speter#include "svn_types.h"
35251881Speter#include "cl.h"
36251881Speter#include "private/svn_client_private.h"
37251881Speter
38251881Speter#include "svn_private_config.h"
39251881Speter
40251881Speter/* A handy constant */
41251881Speterstatic const svn_opt_revision_t unspecified_revision
42251881Speter  = { svn_opt_revision_unspecified, { 0 } };
43251881Speter
44251881Speter
45251881Speter/*** Code. ***/
46251881Speter
47251881Speter/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
48251881Speter * revision. */
49251881Speterstatic svn_error_t *
50251881Speterensure_wc_path_has_repo_revision(const char *path_or_url,
51251881Speter                                 const svn_opt_revision_t *revision,
52251881Speter                                 apr_pool_t *scratch_pool)
53251881Speter{
54251881Speter  if (revision->kind != svn_opt_revision_number
55251881Speter      && revision->kind != svn_opt_revision_date
56251881Speter      && revision->kind != svn_opt_revision_head
57251881Speter      && ! svn_path_is_url(path_or_url))
58251881Speter    return svn_error_createf(
59251881Speter      SVN_ERR_CLIENT_BAD_REVISION, NULL,
60251881Speter      _("Invalid merge source '%s'; a working copy path can only be "
61251881Speter        "used with a repository revision (a number, a date, or head)"),
62251881Speter      svn_dirent_local_style(path_or_url, scratch_pool));
63251881Speter  return SVN_NO_ERROR;
64251881Speter}
65251881Speter
66251881Speter/* Run a merge.
67251881Speter *
68251881Speter * (No docs yet, as this code was just hoisted out of svn_cl__merge().)
69251881Speter *
70251881Speter * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use
71251881Speter * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller.
72251881Speter */
73251881Speterstatic svn_error_t *
74251881Speterrun_merge(svn_boolean_t two_sources_specified,
75251881Speter          const char *sourcepath1,
76251881Speter          svn_opt_revision_t peg_revision1,
77251881Speter          const char *sourcepath2,
78251881Speter          const char *targetpath,
79251881Speter          apr_array_header_t *ranges_to_merge,
80251881Speter          svn_opt_revision_t first_range_start,
81251881Speter          svn_opt_revision_t first_range_end,
82251881Speter          svn_cl__opt_state_t *opt_state,
83251881Speter          apr_array_header_t *options,
84251881Speter          svn_client_ctx_t *ctx,
85251881Speter          apr_pool_t *scratch_pool)
86251881Speter{
87251881Speter  svn_error_t *merge_err;
88251881Speter
89251881Speter  if (opt_state->reintegrate)
90251881Speter    {
91251881Speter      merge_err = svn_cl__deprecated_merge_reintegrate(
92251881Speter                    sourcepath1, &peg_revision1, targetpath,
93251881Speter                    opt_state->dry_run, options, ctx, scratch_pool);
94251881Speter    }
95251881Speter  else if (! two_sources_specified)
96251881Speter    {
97251881Speter      /* If we don't have at least one valid revision range, pick a
98251881Speter         good one that spans the entire set of revisions on our
99251881Speter         source. */
100251881Speter      if ((first_range_start.kind == svn_opt_revision_unspecified)
101251881Speter          && (first_range_end.kind == svn_opt_revision_unspecified))
102251881Speter        {
103251881Speter          ranges_to_merge = NULL;
104251881Speter        }
105251881Speter
106251881Speter      if (opt_state->verbose)
107251881Speter        SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
108251881Speter      merge_err = svn_client_merge_peg5(sourcepath1,
109251881Speter                                        ranges_to_merge,
110251881Speter                                        &peg_revision1,
111251881Speter                                        targetpath,
112251881Speter                                        opt_state->depth,
113251881Speter                                        opt_state->ignore_ancestry,
114251881Speter                                        opt_state->ignore_ancestry,
115251881Speter                                        opt_state->force, /* force_delete */
116251881Speter                                        opt_state->record_only,
117251881Speter                                        opt_state->dry_run,
118251881Speter                                        opt_state->allow_mixed_rev,
119251881Speter                                        options,
120251881Speter                                        ctx,
121251881Speter                                        scratch_pool);
122251881Speter    }
123251881Speter  else
124251881Speter    {
125251881Speter      if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2))
126251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
127251881Speter                                _("Merge sources must both be "
128251881Speter                                  "either paths or URLs"));
129251881Speter
130251881Speter      if (opt_state->verbose)
131251881Speter        SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
132251881Speter      merge_err = svn_client_merge5(sourcepath1,
133251881Speter                                    &first_range_start,
134251881Speter                                    sourcepath2,
135251881Speter                                    &first_range_end,
136251881Speter                                    targetpath,
137251881Speter                                    opt_state->depth,
138251881Speter                                    opt_state->ignore_ancestry,
139251881Speter                                    opt_state->ignore_ancestry,
140251881Speter                                    opt_state->force, /* force_delete */
141251881Speter                                    opt_state->record_only,
142251881Speter                                    opt_state->dry_run,
143251881Speter                                    opt_state->allow_mixed_rev,
144251881Speter                                    options,
145251881Speter                                    ctx,
146251881Speter                                    scratch_pool);
147251881Speter    }
148251881Speter
149251881Speter  return merge_err;
150251881Speter}
151251881Speter
152251881Speter/* This implements the `svn_opt_subcommand_t' interface. */
153251881Spetersvn_error_t *
154251881Spetersvn_cl__merge(apr_getopt_t *os,
155251881Speter              void *baton,
156251881Speter              apr_pool_t *pool)
157251881Speter{
158251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
159251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
160251881Speter  apr_array_header_t *targets;
161251881Speter  const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = "";
162251881Speter  svn_boolean_t two_sources_specified = TRUE;
163251881Speter  svn_error_t *merge_err;
164251881Speter  svn_opt_revision_t first_range_start, first_range_end, peg_revision1,
165251881Speter    peg_revision2;
166251881Speter  apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
167251881Speter  svn_boolean_t has_explicit_target = FALSE;
168251881Speter
169251881Speter  /* Merge doesn't support specifying a revision or revision range
170251881Speter     when using --reintegrate. */
171251881Speter  if (opt_state->reintegrate
172251881Speter      && opt_state->start_revision.kind != svn_opt_revision_unspecified)
173251881Speter    {
174251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
175251881Speter                              _("-r and -c can't be used with --reintegrate"));
176251881Speter    }
177251881Speter
178251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
179251881Speter                                                      opt_state->targets,
180251881Speter                                                      ctx, FALSE, pool));
181251881Speter
182251881Speter  /* For now, we require at least one source.  That may change in
183251881Speter     future versions of Subversion, for example if we have support for
184251881Speter     negated mergeinfo.  See this IRC conversation:
185251881Speter
186251881Speter       <bhuvan>   kfogel: yeah, i think you are correct; we should
187251881Speter                  specify the source url
188251881Speter
189251881Speter       <kfogel>   bhuvan: I'll change the help output and propose for
190251881Speter                  backport.  Thanks.
191251881Speter
192251881Speter       <bhuvan>   kfogel: np; while we are at it, 'svn merge' simply
193251881Speter                  returns nothing; i think we should say: """svn: Not
194251881Speter                  enough arguments provided; try 'svn help' for more
195251881Speter                  info"""
196251881Speter
197251881Speter       <kfogel>   good idea
198251881Speter
199251881Speter       <kfogel>   (in the future, 'svn merge' might actually do
200251881Speter                  something, but that's all the more reason to make
201251881Speter                  sure it errors now)
202251881Speter
203251881Speter       <cmpilato> actually, i'm pretty sure 'svn merge' does something
204251881Speter
205251881Speter       <cmpilato> it says "please merge any unmerged changes from
206251881Speter                  myself to myself."
207251881Speter
208251881Speter       <cmpilato> :-)
209251881Speter
210251881Speter       <kfogel>   har har
211251881Speter
212251881Speter       <cmpilato> kfogel: i was serious.
213251881Speter
214251881Speter       <kfogel>   cmpilato: urrr, uh.  Is that meaningful?  Is there
215251881Speter                  ever a reason for a user to run it?
216251881Speter
217251881Speter       <cmpilato> kfogel: not while we don't have support for negated
218251881Speter                  mergeinfo.
219251881Speter
220251881Speter       <kfogel>   cmpilato: do you concur that until it does something
221251881Speter                  useful it should error?
222251881Speter
223251881Speter       <cmpilato> kfogel: yup.
224251881Speter
225251881Speter       <kfogel>   cool
226251881Speter  */
227251881Speter  if (targets->nelts < 1)
228251881Speter    {
229251881Speter      return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
230251881Speter                              _("Merge source required"));
231251881Speter    }
232251881Speter  else  /* Parse at least one, and possible two, sources. */
233251881Speter    {
234251881Speter      SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
235251881Speter                                 APR_ARRAY_IDX(targets, 0, const char *),
236251881Speter                                 pool));
237251881Speter      if (targets->nelts >= 2)
238251881Speter        SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
239251881Speter                                   APR_ARRAY_IDX(targets, 1, const char *),
240251881Speter                                   pool));
241251881Speter    }
242251881Speter
243251881Speter  /* We could have one or two sources.  Deliberately written to stay
244251881Speter     correct even if we someday permit implied merge source. */
245251881Speter  if (targets->nelts <= 1)
246251881Speter    {
247251881Speter      two_sources_specified = FALSE;
248251881Speter    }
249251881Speter  else if (targets->nelts == 2)
250251881Speter    {
251251881Speter      if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
252251881Speter        two_sources_specified = FALSE;
253251881Speter    }
254251881Speter
255251881Speter  if (opt_state->revision_ranges->nelts > 0)
256251881Speter    {
257251881Speter      first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
258251881Speter                                        svn_opt_revision_range_t *)->start;
259251881Speter      first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
260251881Speter                                      svn_opt_revision_range_t *)->end;
261251881Speter    }
262251881Speter  else
263251881Speter    {
264251881Speter      first_range_start.kind = first_range_end.kind =
265251881Speter        svn_opt_revision_unspecified;
266251881Speter    }
267251881Speter
268251881Speter  /* If revision_ranges has at least one real range at this point, then
269251881Speter     we know the user must have used the '-r' and/or '-c' switch(es).
270251881Speter     This means we're *not* doing two distinct sources. */
271251881Speter  if (first_range_start.kind != svn_opt_revision_unspecified)
272251881Speter    {
273251881Speter      /* A revision *range* is required. */
274251881Speter      if (first_range_end.kind == svn_opt_revision_unspecified)
275251881Speter        return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
276251881Speter                                _("Second revision required"));
277251881Speter
278251881Speter      two_sources_specified = FALSE;
279251881Speter    }
280251881Speter
281251881Speter  if (! two_sources_specified) /* TODO: Switch order of if */
282251881Speter    {
283251881Speter      if (targets->nelts > 2)
284251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
285251881Speter                                _("Too many arguments given"));
286251881Speter
287251881Speter      /* Set the default value for unspecified paths and peg revision. */
288251881Speter      /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge
289251881Speter         SOURCE WCPATH") here. */
290251881Speter      sourcepath2 = sourcepath1;
291251881Speter
292251881Speter      if (peg_revision1.kind == svn_opt_revision_unspecified)
293251881Speter        peg_revision1.kind = svn_path_is_url(sourcepath1)
294251881Speter          ? svn_opt_revision_head : svn_opt_revision_working;
295251881Speter
296251881Speter      if (targets->nelts == 2)
297251881Speter        {
298251881Speter          targetpath = APR_ARRAY_IDX(targets, 1, const char *);
299251881Speter          has_explicit_target = TRUE;
300251881Speter          if (svn_path_is_url(targetpath))
301251881Speter            return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
302251881Speter                                    _("Cannot specify a revision range "
303251881Speter                                      "with two URLs"));
304251881Speter        }
305251881Speter    }
306251881Speter  else /* using @rev syntax */
307251881Speter    {
308251881Speter      if (targets->nelts < 2)
309251881Speter        return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
310251881Speter      if (targets->nelts > 3)
311251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
312251881Speter                                _("Too many arguments given"));
313251881Speter
314251881Speter      first_range_start = peg_revision1;
315251881Speter      first_range_end = peg_revision2;
316251881Speter
317251881Speter      /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit
318251881Speter         revisions--since it ignores local modifications it may not do what
319251881Speter         the user expects.  That is, it doesn't read from the WC itself, it
320251881Speter         reads from the WC's URL.  Forcing the user to specify a repository
321251881Speter         revision should avoid any confusion. */
322251881Speter      SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start,
323251881Speter                                               pool));
324251881Speter      SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
325251881Speter                                               pool));
326251881Speter
327251881Speter      /* Default peg revisions to each URL's youngest revision. */
328251881Speter      if (first_range_start.kind == svn_opt_revision_unspecified)
329251881Speter        first_range_start.kind = svn_opt_revision_head;
330251881Speter      if (first_range_end.kind == svn_opt_revision_unspecified)
331251881Speter        first_range_end.kind = svn_opt_revision_head;
332251881Speter
333251881Speter      /* Decide where to apply the delta (defaulting to "."). */
334251881Speter      if (targets->nelts == 3)
335251881Speter        {
336251881Speter          targetpath = APR_ARRAY_IDX(targets, 2, const char *);
337251881Speter          has_explicit_target = TRUE;
338251881Speter        }
339251881Speter    }
340251881Speter
341251881Speter  /* If no targetpath was specified, see if we can infer it from the
342251881Speter     sourcepaths. */
343251881Speter  if (! has_explicit_target
344251881Speter      && sourcepath1 && sourcepath2
345251881Speter      && strcmp(targetpath, "") == 0)
346251881Speter    {
347251881Speter      /* If the sourcepath is a URL, it can only refer to a target in
348251881Speter         the current working directory or which is the current working
349251881Speter         directory.  However, if the sourcepath is a local path, it can
350251881Speter         refer to a target somewhere deeper in the directory structure. */
351251881Speter      if (svn_path_is_url(sourcepath1))
352251881Speter        {
353251881Speter          const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
354251881Speter          const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
355251881Speter
356251881Speter          if (strcmp(sp1_basename, sp2_basename) == 0)
357251881Speter            {
358251881Speter              const char *target_url;
359251881Speter              const char *target_base;
360251881Speter
361251881Speter              SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
362251881Speter                                                pool, pool));
363251881Speter              target_base = svn_uri_basename(target_url, pool);
364251881Speter
365251881Speter              /* If the basename of the source is the same as the basename of
366251881Speter                 the cwd assume the cwd is the target. */
367251881Speter              if (strcmp(sp1_basename, target_base) != 0)
368251881Speter                {
369251881Speter                  svn_node_kind_t kind;
370251881Speter
371251881Speter                  /* If the basename of the source differs from the basename
372251881Speter                     of the target.  We still might assume the cwd is the
373251881Speter                     target, but first check if there is a file in the cwd
374251881Speter                     with the same name as the source basename.  If there is,
375251881Speter                     then assume that file is the target. */
376251881Speter                  SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool));
377251881Speter                  if (kind == svn_node_file)
378251881Speter                    {
379251881Speter                      targetpath = sp1_basename;
380251881Speter                    }
381251881Speter                }
382251881Speter            }
383251881Speter        }
384251881Speter      else if (strcmp(sourcepath1, sourcepath2) == 0)
385251881Speter        {
386251881Speter          svn_node_kind_t kind;
387251881Speter
388251881Speter          SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
389251881Speter          if (kind == svn_node_file)
390251881Speter            {
391251881Speter              targetpath = sourcepath1;
392251881Speter            }
393251881Speter        }
394251881Speter    }
395251881Speter
396251881Speter  if (opt_state->extensions)
397251881Speter    options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
398251881Speter  else
399251881Speter    options = NULL;
400251881Speter
401251881Speter  /* More input validation. */
402251881Speter  if (opt_state->reintegrate)
403251881Speter    {
404251881Speter      if (opt_state->ignore_ancestry)
405251881Speter        return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
406251881Speter                                _("--reintegrate cannot be used with "
407251881Speter                                  "--ignore-ancestry"));
408251881Speter
409251881Speter      if (opt_state->record_only)
410251881Speter        return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
411251881Speter                                _("--reintegrate cannot be used with "
412251881Speter                                  "--record-only"));
413251881Speter
414251881Speter      if (opt_state->depth != svn_depth_unknown)
415251881Speter        return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
416251881Speter                                _("--depth cannot be used with "
417251881Speter                                  "--reintegrate"));
418251881Speter
419251881Speter      if (opt_state->force)
420251881Speter        return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
421251881Speter                                _("--force cannot be used with "
422251881Speter                                  "--reintegrate"));
423251881Speter
424251881Speter      if (two_sources_specified)
425251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
426251881Speter                                _("--reintegrate can only be used with "
427251881Speter                                  "a single merge source"));
428251881Speter      if (opt_state->allow_mixed_rev)
429251881Speter        return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
430251881Speter                                _("--allow-mixed-revisions cannot be used "
431251881Speter                                  "with --reintegrate"));
432251881Speter    }
433251881Speter
434251881Speter  merge_err = run_merge(two_sources_specified,
435251881Speter                        sourcepath1, peg_revision1,
436251881Speter                        sourcepath2,
437251881Speter                        targetpath,
438251881Speter                        ranges_to_merge, first_range_start, first_range_end,
439251881Speter                        opt_state, options, ctx, pool);
440251881Speter  if (merge_err && merge_err->apr_err
441251881Speter                   == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
442251881Speter    {
443251881Speter      return svn_error_quick_wrap(
444251881Speter               merge_err,
445251881Speter               _("Merge tracking not possible, use --ignore-ancestry or\n"
446251881Speter                 "fix invalid mergeinfo in target with 'svn propset'"));
447251881Speter    }
448251881Speter
449251881Speter  if (!opt_state->quiet)
450251881Speter    {
451251881Speter      svn_error_t *err = svn_cl__notifier_print_conflict_stats(
452251881Speter                           ctx->notify_baton2, pool);
453251881Speter
454251881Speter      merge_err = svn_error_compose_create(merge_err, err);
455251881Speter    }
456251881Speter
457251881Speter  return svn_cl__may_need_force(merge_err);
458251881Speter}
459