]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/svn/merge-cmd.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / svn / merge-cmd.c
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 \f
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 /* A handy constant */
41 static const svn_opt_revision_t unspecified_revision
42   = { svn_opt_revision_unspecified, { 0 } };
43
44 \f
45 /*** Code. ***/
46
47 /* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
48  * revision. */
49 static svn_error_t *
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)
53 {
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));
63   return SVN_NO_ERROR;
64 }
65
66 /* Run a merge.
67  *
68  * (No docs yet, as this code was just hoisted out of svn_cl__merge().)
69  *
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.
72  */
73 static svn_error_t *
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)
86 {
87   svn_error_t *merge_err;
88
89   if (opt_state->reintegrate)
90     {
91       merge_err = svn_cl__deprecated_merge_reintegrate(
92                     sourcepath1, &peg_revision1, targetpath,
93                     opt_state->dry_run, options, ctx, scratch_pool);
94     }
95   else if (! two_sources_specified)
96     {
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
99          source. */
100       if ((first_range_start.kind == svn_opt_revision_unspecified)
101           && (first_range_end.kind == svn_opt_revision_unspecified))
102         {
103           ranges_to_merge = NULL;
104         }
105
106       if (opt_state->verbose)
107         SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
108       merge_err = svn_client_merge_peg5(sourcepath1,
109                                         ranges_to_merge,
110                                         &peg_revision1,
111                                         targetpath,
112                                         opt_state->depth,
113                                         opt_state->ignore_ancestry,
114                                         opt_state->ignore_ancestry,
115                                         opt_state->force, /* force_delete */
116                                         opt_state->record_only,
117                                         opt_state->dry_run,
118                                         opt_state->allow_mixed_rev,
119                                         options,
120                                         ctx,
121                                         scratch_pool);
122     }
123   else
124     {
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"));
129
130       if (opt_state->verbose)
131         SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
132       merge_err = svn_client_merge5(sourcepath1,
133                                     &first_range_start,
134                                     sourcepath2,
135                                     &first_range_end,
136                                     targetpath,
137                                     opt_state->depth,
138                                     opt_state->ignore_ancestry,
139                                     opt_state->ignore_ancestry,
140                                     opt_state->force, /* force_delete */
141                                     opt_state->record_only,
142                                     opt_state->dry_run,
143                                     opt_state->allow_mixed_rev,
144                                     options,
145                                     ctx,
146                                     scratch_pool);
147     }
148
149   return merge_err;
150 }
151
152 /* This implements the `svn_opt_subcommand_t' interface. */
153 svn_error_t *
154 svn_cl__merge(apr_getopt_t *os,
155               void *baton,
156               apr_pool_t *pool)
157 {
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,
165     peg_revision2;
166   apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
167   svn_boolean_t has_explicit_target = FALSE;
168
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)
173     {
174       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
175                               _("-r and -c can't be used with --reintegrate"));
176     }
177
178   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
179                                                       opt_state->targets,
180                                                       ctx, FALSE, pool));
181
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:
185
186        <bhuvan>   kfogel: yeah, i think you are correct; we should
187                   specify the source url
188
189        <kfogel>   bhuvan: I'll change the help output and propose for
190                   backport.  Thanks.
191
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
195                   info"""
196
197        <kfogel>   good idea
198
199        <kfogel>   (in the future, 'svn merge' might actually do
200                   something, but that's all the more reason to make
201                   sure it errors now)
202
203        <cmpilato> actually, i'm pretty sure 'svn merge' does something
204
205        <cmpilato> it says "please merge any unmerged changes from
206                   myself to myself."
207
208        <cmpilato> :-)
209
210        <kfogel>   har har
211
212        <cmpilato> kfogel: i was serious.
213
214        <kfogel>   cmpilato: urrr, uh.  Is that meaningful?  Is there
215                   ever a reason for a user to run it?
216
217        <cmpilato> kfogel: not while we don't have support for negated
218                   mergeinfo.
219
220        <kfogel>   cmpilato: do you concur that until it does something
221                   useful it should error?
222
223        <cmpilato> kfogel: yup.
224
225        <kfogel>   cool
226   */
227   if (targets->nelts < 1)
228     {
229       return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
230                               _("Merge source required"));
231     }
232   else  /* Parse at least one, and possible two, sources. */
233     {
234       SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
235                                  APR_ARRAY_IDX(targets, 0, const char *),
236                                  pool));
237       if (targets->nelts >= 2)
238         SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
239                                    APR_ARRAY_IDX(targets, 1, const char *),
240                                    pool));
241     }
242
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)
246     {
247       two_sources_specified = FALSE;
248     }
249   else if (targets->nelts == 2)
250     {
251       if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
252         two_sources_specified = FALSE;
253     }
254
255   if (opt_state->revision_ranges->nelts > 0)
256     {
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;
261     }
262   else
263     {
264       first_range_start.kind = first_range_end.kind =
265         svn_opt_revision_unspecified;
266     }
267
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)
272     {
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"));
277
278       two_sources_specified = FALSE;
279     }
280
281   if (! two_sources_specified) /* TODO: Switch order of if */
282     {
283       if (targets->nelts > 2)
284         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
285                                 _("Too many arguments given"));
286
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;
291
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;
295
296       if (targets->nelts == 2)
297         {
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 "
303                                       "with two URLs"));
304         }
305     }
306   else /* using @rev syntax */
307     {
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"));
313
314       first_range_start = peg_revision1;
315       first_range_end = peg_revision2;
316
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,
323                                                pool));
324       SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
325                                                pool));
326
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;
332
333       /* Decide where to apply the delta (defaulting to "."). */
334       if (targets->nelts == 3)
335         {
336           targetpath = APR_ARRAY_IDX(targets, 2, const char *);
337           has_explicit_target = TRUE;
338         }
339     }
340
341   /* If no targetpath was specified, see if we can infer it from the
342      sourcepaths. */
343   if (! has_explicit_target
344       && sourcepath1 && sourcepath2
345       && strcmp(targetpath, "") == 0)
346     {
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))
352         {
353           const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
354           const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
355
356           if (strcmp(sp1_basename, sp2_basename) == 0)
357             {
358               const char *target_url;
359               const char *target_base;
360
361               SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
362                                                 pool, pool));
363               target_base = svn_uri_basename(target_url, pool);
364
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)
368                 {
369                   svn_node_kind_t kind;
370
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)
378                     {
379                       targetpath = sp1_basename;
380                     }
381                 }
382             }
383         }
384       else if (strcmp(sourcepath1, sourcepath2) == 0)
385         {
386           svn_node_kind_t kind;
387
388           SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
389           if (kind == svn_node_file)
390             {
391               targetpath = sourcepath1;
392             }
393         }
394     }
395
396   if (opt_state->extensions)
397     options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
398   else
399     options = NULL;
400
401   /* More input validation. */
402   if (opt_state->reintegrate)
403     {
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"));
408
409       if (opt_state->record_only)
410         return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
411                                 _("--reintegrate cannot be used with "
412                                   "--record-only"));
413
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 "
417                                   "--reintegrate"));
418
419       if (opt_state->force)
420         return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
421                                 _("--force cannot be used with "
422                                   "--reintegrate"));
423
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"));
432     }
433
434   merge_err = run_merge(two_sources_specified,
435                         sourcepath1, peg_revision1,
436                         sourcepath2,
437                         targetpath,
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)
442     {
443       return svn_error_quick_wrap(
444                merge_err,
445                _("Merge tracking not possible, use --ignore-ancestry or\n"
446                  "fix invalid mergeinfo in target with 'svn propset'"));
447     }
448
449   if (!opt_state->quiet)
450     {
451       svn_error_t *err = svn_cl__notifier_print_conflict_stats(
452                            ctx->notify_baton2, pool);
453
454       merge_err = svn_error_compose_create(merge_err, err);
455     }
456
457   return svn_cl__may_need_force(merge_err);
458 }