1 | /* |
---|
2 | * merge-cmd.c -- Merging changes into a working copy. |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2000-2007 CollabNet. All rights reserved. |
---|
6 | * |
---|
7 | * This software is licensed as described in the file COPYING, which |
---|
8 | * you should have received as part of this distribution. The terms |
---|
9 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
10 | * If newer versions of this license are posted there, you may use a |
---|
11 | * newer version instead, at your option. |
---|
12 | * |
---|
13 | * This software consists of voluntary contributions made by many |
---|
14 | * individuals. For exact contribution history, see the revision |
---|
15 | * history and logs, available at http://subversion.tigris.org/. |
---|
16 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | /* ==================================================================== */ |
---|
20 | |
---|
21 | |
---|
22 | |
---|
23 | /*** Includes. ***/ |
---|
24 | |
---|
25 | #include "svn_client.h" |
---|
26 | #include "svn_path.h" |
---|
27 | #include "svn_error.h" |
---|
28 | #include "svn_types.h" |
---|
29 | #include "cl.h" |
---|
30 | |
---|
31 | #include "svn_private_config.h" |
---|
32 | |
---|
33 | |
---|
34 | /*** Code. ***/ |
---|
35 | |
---|
36 | |
---|
37 | /* This implements the `svn_opt_subcommand_t' interface. */ |
---|
38 | svn_error_t * |
---|
39 | svn_cl__merge(apr_getopt_t *os, |
---|
40 | void *baton, |
---|
41 | apr_pool_t *pool) |
---|
42 | { |
---|
43 | svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; |
---|
44 | svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; |
---|
45 | apr_array_header_t *targets; |
---|
46 | const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = ""; |
---|
47 | svn_boolean_t two_sources_specified = TRUE; |
---|
48 | svn_error_t *err; |
---|
49 | svn_opt_revision_t first_range_start, first_range_end, peg_revision1, |
---|
50 | peg_revision2; |
---|
51 | apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges; |
---|
52 | |
---|
53 | SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, |
---|
54 | opt_state->targets, |
---|
55 | ctx, pool)); |
---|
56 | |
---|
57 | /* For now, we require at least one source. That may change in |
---|
58 | future versions of Subversion, for example if we have support for |
---|
59 | negated mergeinfo. See this IRC conversation: |
---|
60 | |
---|
61 | <bhuvan> kfogel: yeah, i think you are correct; we should |
---|
62 | specify the source url |
---|
63 | |
---|
64 | <kfogel> bhuvan: I'll change the help output and propose for |
---|
65 | backport. Thanks. |
---|
66 | |
---|
67 | <bhuvan> kfogel: np; while we are at it, 'svn merge' simply |
---|
68 | returns nothing; i think we should say: """svn: Not |
---|
69 | enough arguments provided; try 'svn help' for more |
---|
70 | info""" |
---|
71 | |
---|
72 | <kfogel> good idea |
---|
73 | |
---|
74 | <kfogel> (in the future, 'svn merge' might actually do |
---|
75 | something, but that's all the more reason to make |
---|
76 | sure it errors now) |
---|
77 | |
---|
78 | <cmpilato> actually, i'm pretty sure 'svn merge' does something |
---|
79 | |
---|
80 | <cmpilato> it says "please merge any unmerged changes from |
---|
81 | myself to myself." |
---|
82 | |
---|
83 | <cmpilato> :-) |
---|
84 | |
---|
85 | <kfogel> har har |
---|
86 | |
---|
87 | <cmpilato> kfogel: i was serious. |
---|
88 | |
---|
89 | <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there |
---|
90 | ever a reason for a user to run it? |
---|
91 | |
---|
92 | <cmpilato> kfogel: not while we don't have support for negated |
---|
93 | mergeinfo. |
---|
94 | |
---|
95 | <kfogel> cmpilato: do you concur that until it does something |
---|
96 | useful it should error? |
---|
97 | |
---|
98 | <cmpilato> kfogel: yup. |
---|
99 | |
---|
100 | <kfogel> cool |
---|
101 | */ |
---|
102 | if (targets->nelts < 1) |
---|
103 | { |
---|
104 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, |
---|
105 | _("Merge source required")); |
---|
106 | } |
---|
107 | else /* Parse at least one, and possible two, sources. */ |
---|
108 | { |
---|
109 | SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1, |
---|
110 | APR_ARRAY_IDX(targets, 0, const char *), |
---|
111 | pool)); |
---|
112 | if (targets->nelts >= 2) |
---|
113 | SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2, |
---|
114 | APR_ARRAY_IDX(targets, 1, const char *), |
---|
115 | pool)); |
---|
116 | } |
---|
117 | |
---|
118 | /* We could have one or two sources. Deliberately written to stay |
---|
119 | correct even if we someday permit implied merge source. */ |
---|
120 | if (targets->nelts <= 1) |
---|
121 | { |
---|
122 | two_sources_specified = FALSE; |
---|
123 | } |
---|
124 | else if (targets->nelts == 2) |
---|
125 | { |
---|
126 | if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2)) |
---|
127 | two_sources_specified = FALSE; |
---|
128 | } |
---|
129 | |
---|
130 | if (opt_state->revision_ranges->nelts > 0) |
---|
131 | { |
---|
132 | first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0, |
---|
133 | svn_opt_revision_range_t *)->start; |
---|
134 | first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0, |
---|
135 | svn_opt_revision_range_t *)->end; |
---|
136 | } |
---|
137 | else |
---|
138 | { |
---|
139 | first_range_start.kind = first_range_end.kind = |
---|
140 | svn_opt_revision_unspecified; |
---|
141 | } |
---|
142 | |
---|
143 | /* If revision_ranges has at least one real range at this point, then |
---|
144 | we know the user must have used the '-r' and/or '-c' switch(es). |
---|
145 | This means we're *not* doing two distinct sources. */ |
---|
146 | if (first_range_start.kind != svn_opt_revision_unspecified) |
---|
147 | { |
---|
148 | /* A revision *range* is required. */ |
---|
149 | if (first_range_end.kind == svn_opt_revision_unspecified) |
---|
150 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, |
---|
151 | _("Second revision required")); |
---|
152 | |
---|
153 | two_sources_specified = FALSE; |
---|
154 | } |
---|
155 | |
---|
156 | if (! two_sources_specified) /* TODO: Switch order of if */ |
---|
157 | { |
---|
158 | if (targets->nelts > 2) |
---|
159 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
160 | _("Too many arguments given")); |
---|
161 | |
---|
162 | /* Set the default value for unspecified paths and peg revision. */ |
---|
163 | if (targets->nelts == 0) |
---|
164 | { |
---|
165 | peg_revision1.kind = svn_opt_revision_head; |
---|
166 | } |
---|
167 | else |
---|
168 | { |
---|
169 | /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge |
---|
170 | SOURCE WCPATH") here. */ |
---|
171 | sourcepath2 = sourcepath1; |
---|
172 | |
---|
173 | if (peg_revision1.kind == svn_opt_revision_unspecified) |
---|
174 | peg_revision1.kind = svn_path_is_url(sourcepath1) |
---|
175 | ? svn_opt_revision_head : svn_opt_revision_working; |
---|
176 | |
---|
177 | if (targets->nelts == 2) |
---|
178 | { |
---|
179 | targetpath = APR_ARRAY_IDX(targets, 1, const char *); |
---|
180 | if (svn_path_is_url(targetpath)) |
---|
181 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
182 | _("Cannot specify a revision range " |
---|
183 | "with two URLs")); |
---|
184 | } |
---|
185 | } |
---|
186 | } |
---|
187 | else /* using @rev syntax */ |
---|
188 | { |
---|
189 | if (targets->nelts < 2) |
---|
190 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); |
---|
191 | if (targets->nelts > 3) |
---|
192 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
193 | _("Too many arguments given")); |
---|
194 | |
---|
195 | first_range_start = peg_revision1; |
---|
196 | first_range_end = peg_revision2; |
---|
197 | |
---|
198 | /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit |
---|
199 | revisions--since it ignores local modifications it may not do what |
---|
200 | the user expects. Forcing the user to specify a repository |
---|
201 | revision should avoid any confusion. */ |
---|
202 | if ((first_range_start.kind == svn_opt_revision_unspecified |
---|
203 | && ! svn_path_is_url(sourcepath1)) |
---|
204 | || |
---|
205 | (first_range_end.kind == svn_opt_revision_unspecified |
---|
206 | && ! svn_path_is_url(sourcepath2))) |
---|
207 | return svn_error_create |
---|
208 | (SVN_ERR_CLIENT_BAD_REVISION, 0, |
---|
209 | _("A working copy merge source needs an explicit revision")); |
---|
210 | |
---|
211 | /* Default peg revisions to each URL's youngest revision. */ |
---|
212 | if (first_range_start.kind == svn_opt_revision_unspecified) |
---|
213 | first_range_start.kind = svn_opt_revision_head; |
---|
214 | if (first_range_end.kind == svn_opt_revision_unspecified) |
---|
215 | first_range_end.kind = svn_opt_revision_head; |
---|
216 | |
---|
217 | /* Decide where to apply the delta (defaulting to "."). */ |
---|
218 | if (targets->nelts == 3) |
---|
219 | targetpath = APR_ARRAY_IDX(targets, 2, const char *); |
---|
220 | } |
---|
221 | |
---|
222 | /* If no targetpath was specified, see if we can infer it from the |
---|
223 | sourcepaths. */ |
---|
224 | if (sourcepath1 && sourcepath2 && strcmp(targetpath, "") == 0) |
---|
225 | { |
---|
226 | /* If the sourcepath is a URL, it can only refer to a target in the |
---|
227 | current working directory. |
---|
228 | However, if the sourcepath is a local path, it can refer to a target |
---|
229 | somewhere deeper in the directory structure. */ |
---|
230 | if (svn_path_is_url(sourcepath1)) |
---|
231 | { |
---|
232 | char *sp1_basename, *sp2_basename; |
---|
233 | sp1_basename = svn_path_basename(sourcepath1, pool); |
---|
234 | sp2_basename = svn_path_basename(sourcepath2, pool); |
---|
235 | |
---|
236 | if (strcmp(sp1_basename, sp2_basename) == 0) |
---|
237 | { |
---|
238 | svn_node_kind_t kind; |
---|
239 | const char *decoded_path = svn_path_uri_decode(sp1_basename, pool); |
---|
240 | SVN_ERR(svn_io_check_path(decoded_path, &kind, pool)); |
---|
241 | if (kind == svn_node_file) |
---|
242 | { |
---|
243 | targetpath = decoded_path; |
---|
244 | } |
---|
245 | } |
---|
246 | } |
---|
247 | else if (strcmp(sourcepath1, sourcepath2) == 0) |
---|
248 | { |
---|
249 | svn_node_kind_t kind; |
---|
250 | const char *decoded_path = svn_path_uri_decode(sourcepath1, pool); |
---|
251 | SVN_ERR(svn_io_check_path(decoded_path, &kind, pool)); |
---|
252 | if (kind == svn_node_file) |
---|
253 | { |
---|
254 | targetpath = decoded_path; |
---|
255 | } |
---|
256 | } |
---|
257 | } |
---|
258 | |
---|
259 | if (! opt_state->quiet) |
---|
260 | svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, FALSE, |
---|
261 | FALSE, FALSE, pool); |
---|
262 | |
---|
263 | if (opt_state->extensions) |
---|
264 | options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); |
---|
265 | else |
---|
266 | options = NULL; |
---|
267 | |
---|
268 | if (! two_sources_specified) /* TODO: Switch order of if */ |
---|
269 | { |
---|
270 | /* If we don't have a source, use the target as the source. */ |
---|
271 | if (! sourcepath1) |
---|
272 | sourcepath1 = targetpath; |
---|
273 | |
---|
274 | /* If we don't have at least one valid revision range, pick a |
---|
275 | good one that spans the entire set of revisions on our |
---|
276 | source. */ |
---|
277 | if ((first_range_start.kind == svn_opt_revision_unspecified) |
---|
278 | && (first_range_end.kind == svn_opt_revision_unspecified)) |
---|
279 | { |
---|
280 | svn_opt_revision_range_t *range = apr_pcalloc(pool, sizeof(*range)); |
---|
281 | ranges_to_merge = apr_array_make(pool, 1, sizeof(range)); |
---|
282 | range->start.kind = svn_opt_revision_number; |
---|
283 | range->start.value.number = 1; |
---|
284 | range->end = peg_revision1; |
---|
285 | APR_ARRAY_PUSH(ranges_to_merge, svn_opt_revision_range_t *) = range; |
---|
286 | } |
---|
287 | |
---|
288 | if (opt_state->reintegrate) |
---|
289 | { |
---|
290 | if (opt_state->depth != svn_depth_unknown) |
---|
291 | return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, |
---|
292 | _("--depth cannot be used with " |
---|
293 | "--reintegrate")); |
---|
294 | |
---|
295 | if (opt_state->force) |
---|
296 | return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, |
---|
297 | _("--force cannot be used with " |
---|
298 | "--reintegrate")); |
---|
299 | |
---|
300 | err = svn_client_merge_reintegrate(sourcepath1, |
---|
301 | &peg_revision1, |
---|
302 | targetpath, |
---|
303 | opt_state->dry_run, |
---|
304 | options, ctx, pool); |
---|
305 | } |
---|
306 | else |
---|
307 | err = svn_client_merge_peg3(sourcepath1, |
---|
308 | ranges_to_merge, |
---|
309 | &peg_revision1, |
---|
310 | targetpath, |
---|
311 | opt_state->depth, |
---|
312 | opt_state->ignore_ancestry, |
---|
313 | opt_state->force, |
---|
314 | opt_state->record_only, |
---|
315 | opt_state->dry_run, |
---|
316 | options, |
---|
317 | ctx, |
---|
318 | pool); |
---|
319 | } |
---|
320 | else |
---|
321 | { |
---|
322 | err = svn_client_merge3(sourcepath1, |
---|
323 | &first_range_start, |
---|
324 | sourcepath2, |
---|
325 | &first_range_end, |
---|
326 | targetpath, |
---|
327 | opt_state->depth, |
---|
328 | opt_state->ignore_ancestry, |
---|
329 | opt_state->force, |
---|
330 | opt_state->record_only, |
---|
331 | opt_state->dry_run, |
---|
332 | options, |
---|
333 | ctx, |
---|
334 | pool); |
---|
335 | } |
---|
336 | |
---|
337 | if (err && (! opt_state->reintegrate)) |
---|
338 | return svn_cl__may_need_force(err); |
---|
339 | |
---|
340 | return err; |
---|
341 | } |
---|