]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svn/merge-cmd.c
MFV r316918: 7990 libzfs: snapspec_cb() does not need to call zfs_strdup()
[FreeBSD/FreeBSD.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 (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. */
154 svn_error_t *
155 svn_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 }