]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/merge-cmd.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 \f
41 /*** Code. ***/
42
43 /* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
44  * revision. */
45 static svn_error_t *
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)
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  */
69 static svn_error_t *
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)
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. */
149 svn_error_t *
150 svn_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 }