1 /*
2 * copy.c: copy/move wrappers around wc 'copy' functionality.
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
28 /*** Includes. ***/
29
30 #include <string.h>
31 #include "svn_hash.h"
32 #include "svn_client.h"
33 #include "svn_error.h"
34 #include "svn_error_codes.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_opt.h"
38 #include "svn_time.h"
39 #include "svn_props.h"
40 #include "svn_mergeinfo.h"
41 #include "svn_pools.h"
42
43 #include "client.h"
44 #include "mergeinfo.h"
45
46 #include "svn_private_config.h"
47 #include "private/svn_wc_private.h"
48 #include "private/svn_ra_private.h"
49 #include "private/svn_mergeinfo_private.h"
50 #include "private/svn_client_private.h"
51
52
53 /*
54 * OUR BASIC APPROACH TO COPIES
55 * ============================
56 *
57 * for each source/destination pair
58 * if (not exist src_path)
59 * return ERR_BAD_SRC error
60 *
61 * if (exist dst_path)
62 * return ERR_OBSTRUCTION error
63 * else
64 * copy src_path into parent_of_dst_path as basename (dst_path)
65 *
66 * if (this is a move)
67 * delete src_path
68 */
69
70
71
72 /*** Code. ***/
73
74 /* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
75 MERGEINFO to any mergeinfo pre-existing in the WC. */
76 static svn_error_t *
extend_wc_mergeinfo(const char * target_abspath,apr_hash_t * mergeinfo,svn_client_ctx_t * ctx,apr_pool_t * pool)77 extend_wc_mergeinfo(const char *target_abspath,
78 apr_hash_t *mergeinfo,
79 svn_client_ctx_t *ctx,
80 apr_pool_t *pool)
81 {
82 apr_hash_t *wc_mergeinfo;
83
84 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
85 updating it. */
86 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
87 target_abspath, pool, pool));
88
89 /* Combine the provided mergeinfo with any mergeinfo from the WC. */
90 if (wc_mergeinfo && mergeinfo)
91 SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool));
92 else if (! wc_mergeinfo)
93 wc_mergeinfo = mergeinfo;
94
95 return svn_error_trace(
96 svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
97 FALSE, ctx, pool));
98 }
99
100 /* Find the longest common ancestor of paths in COPY_PAIRS. If
101 SRC_ANCESTOR is NULL, ignore source paths in this calculation. If
102 DST_ANCESTOR is NULL, ignore destination paths in this calculation.
103 COMMON_ANCESTOR will be the common ancestor of both the
104 SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not
105 NULL.
106 */
107 static svn_error_t *
get_copy_pair_ancestors(const apr_array_header_t * copy_pairs,const char ** src_ancestor,const char ** dst_ancestor,const char ** common_ancestor,apr_pool_t * pool)108 get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
109 const char **src_ancestor,
110 const char **dst_ancestor,
111 const char **common_ancestor,
112 apr_pool_t *pool)
113 {
114 apr_pool_t *subpool = svn_pool_create(pool);
115 svn_client__copy_pair_t *first;
116 const char *first_dst;
117 const char *first_src;
118 const char *top_dst;
119 svn_boolean_t src_is_url;
120 svn_boolean_t dst_is_url;
121 char *top_src;
122 int i;
123
124 first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
125
126 /* Because all the destinations are in the same directory, we can easily
127 determine their common ancestor. */
128 first_dst = first->dst_abspath_or_url;
129 dst_is_url = svn_path_is_url(first_dst);
130
131 if (copy_pairs->nelts == 1)
132 top_dst = apr_pstrdup(subpool, first_dst);
133 else
134 top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
135 : svn_dirent_dirname(first_dst, subpool);
136
137 /* Sources can came from anywhere, so we have to actually do some
138 work for them. */
139 first_src = first->src_abspath_or_url;
140 src_is_url = svn_path_is_url(first_src);
141 top_src = apr_pstrdup(subpool, first_src);
142 for (i = 1; i < copy_pairs->nelts; i++)
143 {
144 /* We don't need to clear the subpool here for several reasons:
145 1) If we do, we can't use it to allocate the initial versions of
146 top_src and top_dst (above).
147 2) We don't return any errors in the following loop, so we
148 are guanteed to destroy the subpool at the end of this function.
149 3) The number of iterations is likely to be few, and the loop will
150 be through quickly, so memory leakage will not be significant,
151 in time or space.
152 */
153 const svn_client__copy_pair_t *pair =
154 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
155
156 top_src = src_is_url
157 ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
158 subpool)
159 : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
160 subpool);
161 }
162
163 if (src_ancestor)
164 *src_ancestor = apr_pstrdup(pool, top_src);
165
166 if (dst_ancestor)
167 *dst_ancestor = apr_pstrdup(pool, top_dst);
168
169 if (common_ancestor)
170 *common_ancestor =
171 src_is_url
172 ? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
173 : svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
174
175 svn_pool_destroy(subpool);
176
177 return SVN_NO_ERROR;
178 }
179
180 /* Quote a string if it would be handled as multiple or different tokens
181 during externals parsing */
182 static const char *
maybe_quote(const char * value,apr_pool_t * result_pool)183 maybe_quote(const char *value,
184 apr_pool_t *result_pool)
185 {
186 apr_status_t status;
187 char **argv;
188
189 status = apr_tokenize_to_argv(value, &argv, result_pool);
190
191 if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0)
192 return apr_pstrdup(result_pool, value);
193
194 {
195 svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool);
196 const char *c;
197
198 svn_stringbuf_appendbyte(sb, '\"');
199
200 for (c = value; *c; c++)
201 {
202 if (*c == '\\' || *c == '\"' || *c == '\'')
203 svn_stringbuf_appendbyte(sb, '\\');
204
205 svn_stringbuf_appendbyte(sb, *c);
206 }
207
208 svn_stringbuf_appendbyte(sb, '\"');
209
210 #ifdef SVN_DEBUG
211 status = apr_tokenize_to_argv(sb->data, &argv, result_pool);
212
213 SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1]
214 && !strcmp(argv[0], value));
215 #endif
216
217 return sb->data;
218 }
219 }
220
221 /* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for
222 * use as a line in an svn:externals property, based on the external item
223 * ITEM and the additional parser information in INFO. Pin the external
224 * to EXTERNAL_PEGREV. Use POOL for all allocations. */
225 static svn_error_t *
make_external_description(const char ** new_external_description,const char * local_abspath_or_url,svn_wc_external_item2_t * item,svn_wc__externals_parser_info_t * info,svn_opt_revision_t external_pegrev,apr_pool_t * pool)226 make_external_description(const char **new_external_description,
227 const char *local_abspath_or_url,
228 svn_wc_external_item2_t *item,
229 svn_wc__externals_parser_info_t *info,
230 svn_opt_revision_t external_pegrev,
231 apr_pool_t *pool)
232 {
233 const char *rev_str;
234 const char *peg_rev_str;
235
236 switch (info->format)
237 {
238 case svn_wc__external_description_format_1:
239 if (external_pegrev.kind == svn_opt_revision_unspecified)
240 {
241 /* If info->rev_str is NULL, this yields an empty string. */
242 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
243 }
244 else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
245 rev_str = apr_psprintf(pool, "%s ", info->rev_str);
246 else
247 {
248 /* ### can't handle svn_opt_revision_date without info->rev_str */
249 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
250 rev_str = apr_psprintf(pool, "-r%ld ",
251 external_pegrev.value.number);
252 }
253
254 *new_external_description =
255 apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool),
256 rev_str,
257 maybe_quote(item->url, pool));
258 break;
259
260 case svn_wc__external_description_format_2:
261 if (external_pegrev.kind == svn_opt_revision_unspecified)
262 {
263 /* If info->rev_str is NULL, this yields an empty string. */
264 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
265 }
266 else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
267 rev_str = apr_psprintf(pool, "%s ", info->rev_str);
268 else
269 rev_str = "";
270
271 if (external_pegrev.kind == svn_opt_revision_unspecified)
272 peg_rev_str = info->peg_rev_str ? info->peg_rev_str : "";
273 else if (info->peg_rev_str &&
274 item->peg_revision.kind != svn_opt_revision_head)
275 peg_rev_str = info->peg_rev_str;
276 else
277 {
278 /* ### can't handle svn_opt_revision_date without info->rev_str */
279 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
280 peg_rev_str = apr_psprintf(pool, "@%ld",
281 external_pegrev.value.number);
282 }
283
284 *new_external_description =
285 apr_psprintf(pool, "%s%s %s\n", rev_str,
286 maybe_quote(apr_psprintf(pool, "%s%s", item->url,
287 peg_rev_str),
288 pool),
289 maybe_quote(item->target_dir, pool));
290 break;
291
292 default:
293 return svn_error_createf(
294 SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
295 _("%s property defined at '%s' is using an unsupported "
296 "syntax"), SVN_PROP_EXTERNALS,
297 svn_dirent_local_style(local_abspath_or_url, pool));
298 }
299
300 return SVN_NO_ERROR;
301 }
302
303 /* Pin all externals listed in EXTERNALS_PROP_VAL to their
304 * last-changed revision. Set *PINNED_EXTERNALS to a new property
305 * value allocated in RESULT_POOL, or to NULL if none of the externals
306 * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the
307 * path or URL defining the svn:externals property. Use SCRATCH_POOL
308 * for temporary allocations.
309 */
310 static svn_error_t *
pin_externals_prop(svn_string_t ** pinned_externals,svn_string_t * externals_prop_val,const apr_hash_t * externals_to_pin,const char * repos_root_url,const char * local_abspath_or_url,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)311 pin_externals_prop(svn_string_t **pinned_externals,
312 svn_string_t *externals_prop_val,
313 const apr_hash_t *externals_to_pin,
314 const char *repos_root_url,
315 const char *local_abspath_or_url,
316 svn_client_ctx_t *ctx,
317 apr_pool_t *result_pool,
318 apr_pool_t *scratch_pool)
319 {
320 svn_stringbuf_t *buf;
321 apr_array_header_t *external_items;
322 apr_array_header_t *parser_infos;
323 apr_array_header_t *items_to_pin;
324 int pinned_items;
325 int i;
326 apr_pool_t *iterpool;
327
328 SVN_ERR(svn_wc__parse_externals_description(&external_items,
329 &parser_infos,
330 local_abspath_or_url,
331 externals_prop_val->data,
332 FALSE /* canonicalize_url */,
333 scratch_pool));
334
335 if (externals_to_pin)
336 {
337 items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin,
338 local_abspath_or_url);
339 if (!items_to_pin)
340 {
341 /* No pinning at all for this path. */
342 *pinned_externals = NULL;
343 return SVN_NO_ERROR;
344 }
345 }
346 else
347 items_to_pin = NULL;
348
349 buf = svn_stringbuf_create_empty(scratch_pool);
350 iterpool = svn_pool_create(scratch_pool);
351 pinned_items = 0;
352 for (i = 0; i < external_items->nelts; i++)
353 {
354 svn_wc_external_item2_t *item;
355 svn_wc__externals_parser_info_t *info;
356 svn_opt_revision_t external_pegrev;
357 const char *pinned_desc;
358
359 svn_pool_clear(iterpool);
360
361 item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
362 info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *);
363
364 if (items_to_pin)
365 {
366 int j;
367 svn_wc_external_item2_t *item_to_pin = NULL;
368
369 for (j = 0; j < items_to_pin->nelts; j++)
370 {
371 svn_wc_external_item2_t *const current =
372 APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *);
373
374
375 if (current
376 && 0 == strcmp(item->url, current->url)
377 && 0 == strcmp(item->target_dir, current->target_dir))
378 {
379 item_to_pin = current;
380 break;
381 }
382 }
383
384 /* If this item is not in our list of external items to pin then
385 * simply keep the external at its original value. */
386 if (!item_to_pin)
387 {
388 const char *desc;
389
390 external_pegrev.kind = svn_opt_revision_unspecified;
391 SVN_ERR(make_external_description(&desc, local_abspath_or_url,
392 item, info, external_pegrev,
393 iterpool));
394 svn_stringbuf_appendcstr(buf, desc);
395 continue;
396 }
397 }
398
399 if (item->peg_revision.kind == svn_opt_revision_date)
400 {
401 /* Already pinned ... copy the peg date. */
402 external_pegrev.kind = svn_opt_revision_date;
403 external_pegrev.value.date = item->peg_revision.value.date;
404 }
405 else if (item->peg_revision.kind == svn_opt_revision_number)
406 {
407 /* Already pinned ... copy the peg revision number. */
408 external_pegrev.kind = svn_opt_revision_number;
409 external_pegrev.value.number = item->peg_revision.value.number;
410 }
411 else
412 {
413 SVN_ERR_ASSERT(
414 item->peg_revision.kind == svn_opt_revision_head ||
415 item->peg_revision.kind == svn_opt_revision_unspecified);
416
417 /* We're actually going to change the peg revision. */
418 ++pinned_items;
419
420 if (svn_path_is_url(local_abspath_or_url))
421 {
422 const char *resolved_url;
423 svn_ra_session_t *external_ra_session;
424 svn_revnum_t latest_revnum;
425
426 SVN_ERR(svn_wc__resolve_relative_external_url(
427 &resolved_url, item, repos_root_url,
428 local_abspath_or_url, iterpool, iterpool));
429 SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session,
430 NULL, resolved_url,
431 NULL, NULL, FALSE,
432 FALSE, ctx,
433 iterpool,
434 iterpool));
435 SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
436 &latest_revnum,
437 iterpool));
438
439 external_pegrev.kind = svn_opt_revision_number;
440 external_pegrev.value.number = latest_revnum;
441 }
442 else
443 {
444 const char *external_abspath;
445 svn_node_kind_t external_kind;
446 svn_revnum_t external_checked_out_rev;
447
448 external_abspath = svn_dirent_join(local_abspath_or_url,
449 item->target_dir,
450 iterpool);
451 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL,
452 NULL, NULL, ctx->wc_ctx,
453 local_abspath_or_url,
454 external_abspath, TRUE,
455 iterpool,
456 iterpool));
457 if (external_kind == svn_node_none)
458 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
459 NULL,
460 _("Cannot pin external '%s' defined "
461 "in %s at '%s' because it is not "
462 "checked out in the working copy "
463 "at '%s'"),
464 item->url, SVN_PROP_EXTERNALS,
465 svn_dirent_local_style(
466 local_abspath_or_url, iterpool),
467 svn_dirent_local_style(
468 external_abspath, iterpool));
469 else if (external_kind == svn_node_dir)
470 {
471 svn_boolean_t is_switched;
472 svn_boolean_t is_modified;
473 svn_revnum_t min_rev;
474 svn_revnum_t max_rev;
475
476 /* Perform some sanity checks on the checked-out external. */
477
478 SVN_ERR(svn_wc__has_switched_subtrees(&is_switched,
479 ctx->wc_ctx,
480 external_abspath, NULL,
481 iterpool));
482 if (is_switched)
483 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
484 NULL,
485 _("Cannot pin external '%s' defined "
486 "in %s at '%s' because '%s' has "
487 "switched subtrees (switches "
488 "cannot be represented in %s)"),
489 item->url, SVN_PROP_EXTERNALS,
490 svn_dirent_local_style(
491 local_abspath_or_url, iterpool),
492 svn_dirent_local_style(
493 external_abspath, iterpool),
494 SVN_PROP_EXTERNALS);
495
496 SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
497 external_abspath, TRUE,
498 ctx->cancel_func,
499 ctx->cancel_baton,
500 iterpool));
501 if (is_modified)
502 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
503 NULL,
504 _("Cannot pin external '%s' defined "
505 "in %s at '%s' because '%s' has "
506 "local modifications (local "
507 "modifications cannot be "
508 "represented in %s)"),
509 item->url, SVN_PROP_EXTERNALS,
510 svn_dirent_local_style(
511 local_abspath_or_url, iterpool),
512 svn_dirent_local_style(
513 external_abspath, iterpool),
514 SVN_PROP_EXTERNALS);
515
516 SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx,
517 external_abspath, FALSE,
518 iterpool));
519 if (min_rev != max_rev)
520 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
521 NULL,
522 _("Cannot pin external '%s' defined "
523 "in %s at '%s' because '%s' is a "
524 "mixed-revision working copy "
525 "(mixed-revisions cannot be "
526 "represented in %s)"),
527 item->url, SVN_PROP_EXTERNALS,
528 svn_dirent_local_style(
529 local_abspath_or_url, iterpool),
530 svn_dirent_local_style(
531 external_abspath, iterpool),
532 SVN_PROP_EXTERNALS);
533 external_checked_out_rev = min_rev;
534 }
535 else
536 {
537 SVN_ERR_ASSERT(external_kind == svn_node_file);
538 SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev,
539 NULL, NULL, NULL,
540 ctx->wc_ctx, external_abspath,
541 iterpool, iterpool));
542 }
543
544 external_pegrev.kind = svn_opt_revision_number;
545 external_pegrev.value.number = external_checked_out_rev;
546 }
547 }
548
549 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date ||
550 external_pegrev.kind == svn_opt_revision_number);
551
552 SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url,
553 item, info, external_pegrev, iterpool));
554
555 svn_stringbuf_appendcstr(buf, pinned_desc);
556 }
557 svn_pool_destroy(iterpool);
558
559 if (pinned_items > 0)
560 *pinned_externals = svn_string_create_from_buf(buf, result_pool);
561 else
562 *pinned_externals = NULL;
563
564 return SVN_NO_ERROR;
565 }
566
567 /* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths
568 * to svn:externals property values (as const char *), where some or all
569 * external references have been pinned.
570 * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals
571 * mentioned in EXTERNALS_TO_PIN.
572 * The pinning operation takes place as part of the copy operation for
573 * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL
574 * to contact the repository containing the externals definition, if neccesary.
575 * Use CX to fopen additional RA sessions to external repositories, if
576 * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL.
577 * Use SCRATCH_POOL for temporary allocations. */
578 static svn_error_t *
resolve_pinned_externals(apr_hash_t ** pinned_externals,const apr_hash_t * externals_to_pin,const svn_client__copy_pair_t * pair,svn_ra_session_t * ra_session,const char * repos_root_url,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)579 resolve_pinned_externals(apr_hash_t **pinned_externals,
580 const apr_hash_t *externals_to_pin,
581 const svn_client__copy_pair_t *pair,
582 svn_ra_session_t *ra_session,
583 const char *repos_root_url,
584 svn_client_ctx_t *ctx,
585 apr_pool_t *result_pool,
586 apr_pool_t *scratch_pool)
587 {
588 const char *old_url = NULL;
589 apr_hash_t *externals_props;
590 apr_hash_index_t *hi;
591 apr_pool_t *iterpool;
592
593 *pinned_externals = apr_hash_make(result_pool);
594
595 if (svn_path_is_url(pair->src_abspath_or_url))
596 {
597 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
598 pair->src_abspath_or_url,
599 scratch_pool));
600 externals_props = apr_hash_make(scratch_pool);
601 SVN_ERR(svn_client__remote_propget(externals_props, NULL,
602 SVN_PROP_EXTERNALS,
603 pair->src_abspath_or_url, "",
604 svn_node_dir,
605 pair->src_revnum,
606 ra_session,
607 svn_depth_infinity,
608 scratch_pool,
609 scratch_pool));
610 }
611 else
612 {
613 SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
614 ctx->wc_ctx,
615 pair->src_abspath_or_url,
616 svn_depth_infinity,
617 scratch_pool, scratch_pool));
618
619 /* ### gather_definitions returns propvals as const char * */
620 for (hi = apr_hash_first(scratch_pool, externals_props);
621 hi;
622 hi = apr_hash_next(hi))
623 {
624 const char *local_abspath_or_url = apr_hash_this_key(hi);
625 const char *propval = apr_hash_this_val(hi);
626 svn_string_t *new_propval = svn_string_create(propval, scratch_pool);
627
628 svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
629 }
630 }
631
632 if (apr_hash_count(externals_props) == 0)
633 {
634 if (old_url)
635 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
636 return SVN_NO_ERROR;
637 }
638
639 iterpool = svn_pool_create(scratch_pool);
640 for (hi = apr_hash_first(scratch_pool, externals_props);
641 hi;
642 hi = apr_hash_next(hi))
643 {
644 const char *local_abspath_or_url = apr_hash_this_key(hi);
645 svn_string_t *externals_propval = apr_hash_this_val(hi);
646 const char *relpath;
647 svn_string_t *new_propval;
648
649 svn_pool_clear(iterpool);
650
651 SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
652 externals_to_pin,
653 repos_root_url, local_abspath_or_url, ctx,
654 result_pool, iterpool));
655 if (new_propval)
656 {
657 if (svn_path_is_url(pair->src_abspath_or_url))
658 relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url,
659 local_abspath_or_url,
660 result_pool);
661 else
662 relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
663 local_abspath_or_url);
664 SVN_ERR_ASSERT(relpath);
665
666 svn_hash_sets(*pinned_externals, relpath, new_propval);
667 }
668 }
669 svn_pool_destroy(iterpool);
670
671 if (old_url)
672 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
673
674 return SVN_NO_ERROR;
675 }
676
677
678
679 /* The guts of do_wc_to_wc_copies */
680 static svn_error_t *
do_wc_to_wc_copies_with_write_lock(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,const char * dst_parent,svn_boolean_t metadata_only,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)681 do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
682 const apr_array_header_t *copy_pairs,
683 const char *dst_parent,
684 svn_boolean_t metadata_only,
685 svn_boolean_t pin_externals,
686 const apr_hash_t *externals_to_pin,
687 svn_client_ctx_t *ctx,
688 apr_pool_t *scratch_pool)
689 {
690 int i;
691 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
692 svn_error_t *err = SVN_NO_ERROR;
693
694 for (i = 0; i < copy_pairs->nelts; i++)
695 {
696 const char *dst_abspath;
697 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
698 svn_client__copy_pair_t *);
699 apr_hash_t *pinned_externals = NULL;
700
701 svn_pool_clear(iterpool);
702
703 /* Check for cancellation */
704 if (ctx->cancel_func)
705 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
706
707 if (pin_externals)
708 {
709 const char *repos_root_url;
710
711 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url,
712 NULL, NULL, NULL, ctx->wc_ctx,
713 pair->src_abspath_or_url, FALSE,
714 scratch_pool, iterpool));
715 SVN_ERR(resolve_pinned_externals(&pinned_externals,
716 externals_to_pin, pair, NULL,
717 repos_root_url, ctx,
718 iterpool, iterpool));
719 }
720
721 /* Perform the copy */
722 dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
723 iterpool);
724 *timestamp_sleep = TRUE;
725 err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
726 metadata_only,
727 ctx->cancel_func, ctx->cancel_baton,
728 ctx->notify_func2, ctx->notify_baton2, iterpool);
729 if (err)
730 break;
731
732 if (pinned_externals)
733 {
734 apr_hash_index_t *hi;
735
736 for (hi = apr_hash_first(iterpool, pinned_externals);
737 hi;
738 hi = apr_hash_next(hi))
739 {
740 const char *dst_relpath = apr_hash_this_key(hi);
741 svn_string_t *externals_propval = apr_hash_this_val(hi);
742 const char *local_abspath;
743
744 local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
745 dst_relpath, iterpool);
746 /* ### use a work queue? */
747 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
748 SVN_PROP_EXTERNALS, externals_propval,
749 svn_depth_empty, TRUE /* skip_checks */,
750 NULL /* changelist_filter */,
751 ctx->cancel_func, ctx->cancel_baton,
752 NULL, NULL, /* no extra notification */
753 iterpool));
754 }
755 }
756 }
757 svn_pool_destroy(iterpool);
758
759 SVN_ERR(err);
760 return SVN_NO_ERROR;
761 }
762
763 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
764 allocations. */
765 static svn_error_t *
do_wc_to_wc_copies(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,svn_boolean_t metadata_only,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * pool)766 do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
767 const apr_array_header_t *copy_pairs,
768 svn_boolean_t metadata_only,
769 svn_boolean_t pin_externals,
770 const apr_hash_t *externals_to_pin,
771 svn_client_ctx_t *ctx,
772 apr_pool_t *pool)
773 {
774 const char *dst_parent, *dst_parent_abspath;
775
776 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
777 if (copy_pairs->nelts == 1)
778 dst_parent = svn_dirent_dirname(dst_parent, pool);
779
780 SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
781
782 SVN_WC__CALL_WITH_WRITE_LOCK(
783 do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
784 metadata_only, pin_externals,
785 externals_to_pin, ctx, pool),
786 ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
787
788 return SVN_NO_ERROR;
789 }
790
791 /* The locked bit of do_wc_to_wc_moves. */
792 static svn_error_t *
do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t * pair,const char * dst_parent_abspath,svn_boolean_t lock_src,svn_boolean_t lock_dst,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)793 do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
794 const char *dst_parent_abspath,
795 svn_boolean_t lock_src,
796 svn_boolean_t lock_dst,
797 svn_boolean_t allow_mixed_revisions,
798 svn_boolean_t metadata_only,
799 svn_client_ctx_t *ctx,
800 apr_pool_t *scratch_pool)
801 {
802 const char *dst_abspath;
803
804 dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
805 scratch_pool);
806
807 SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
808 dst_abspath, metadata_only,
809 allow_mixed_revisions,
810 ctx->cancel_func, ctx->cancel_baton,
811 ctx->notify_func2, ctx->notify_baton2,
812 scratch_pool));
813
814 return SVN_NO_ERROR;
815 }
816
817 /* Wrapper to add an optional second lock */
818 static svn_error_t *
do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t * pair,const char * dst_parent_abspath,svn_boolean_t lock_src,svn_boolean_t lock_dst,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)819 do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
820 const char *dst_parent_abspath,
821 svn_boolean_t lock_src,
822 svn_boolean_t lock_dst,
823 svn_boolean_t allow_mixed_revisions,
824 svn_boolean_t metadata_only,
825 svn_client_ctx_t *ctx,
826 apr_pool_t *scratch_pool)
827 {
828 if (lock_dst)
829 SVN_WC__CALL_WITH_WRITE_LOCK(
830 do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
831 lock_dst, allow_mixed_revisions,
832 metadata_only,
833 ctx, scratch_pool),
834 ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
835 else
836 SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
837 lock_dst, allow_mixed_revisions,
838 metadata_only,
839 ctx, scratch_pool));
840
841 return SVN_NO_ERROR;
842 }
843
844 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
845 afterwards. Use POOL for temporary allocations. */
846 static svn_error_t *
do_wc_to_wc_moves(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,const char * dst_path,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * pool)847 do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
848 const apr_array_header_t *copy_pairs,
849 const char *dst_path,
850 svn_boolean_t allow_mixed_revisions,
851 svn_boolean_t metadata_only,
852 svn_client_ctx_t *ctx,
853 apr_pool_t *pool)
854 {
855 int i;
856 apr_pool_t *iterpool = svn_pool_create(pool);
857 svn_error_t *err = SVN_NO_ERROR;
858
859 for (i = 0; i < copy_pairs->nelts; i++)
860 {
861 const char *src_parent_abspath;
862 svn_boolean_t lock_src, lock_dst;
863 const char *src_wcroot_abspath;
864 const char *dst_wcroot_abspath;
865
866 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
867 svn_client__copy_pair_t *);
868 svn_pool_clear(iterpool);
869
870 /* Check for cancellation */
871 if (ctx->cancel_func)
872 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
873
874 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
875 iterpool);
876
877 SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
878 ctx->wc_ctx, src_parent_abspath,
879 iterpool, iterpool));
880 SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
881 ctx->wc_ctx, pair->dst_parent_abspath,
882 iterpool, iterpool));
883
884 /* We now need to lock the right combination of batons.
885 Four cases:
886 1) src_parent == dst_parent
887 2) src_parent is parent of dst_parent
888 3) dst_parent is parent of src_parent
889 4) src_parent and dst_parent are disjoint
890 We can handle 1) as either 2) or 3) */
891 if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
892 || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
893 NULL)
894 && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
895 NULL)))
896 {
897 lock_src = TRUE;
898 lock_dst = FALSE;
899 }
900 else if (svn_dirent_is_child(pair->dst_parent_abspath,
901 src_parent_abspath, NULL)
902 && !svn_dirent_is_child(pair->dst_parent_abspath,
903 src_wcroot_abspath, NULL))
904 {
905 lock_src = FALSE;
906 lock_dst = TRUE;
907 }
908 else
909 {
910 lock_src = TRUE;
911 lock_dst = TRUE;
912 }
913
914 *timestamp_sleep = TRUE;
915
916 /* Perform the copy and then the delete. */
917 if (lock_src)
918 SVN_WC__CALL_WITH_WRITE_LOCK(
919 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
920 lock_src, lock_dst,
921 allow_mixed_revisions,
922 metadata_only,
923 ctx, iterpool),
924 ctx->wc_ctx, src_parent_abspath,
925 FALSE, iterpool);
926 else
927 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
928 lock_src, lock_dst,
929 allow_mixed_revisions,
930 metadata_only,
931 ctx, iterpool));
932
933 }
934 svn_pool_destroy(iterpool);
935
936 return svn_error_trace(err);
937 }
938
939 /* Verify that the destinations stored in COPY_PAIRS are valid working copy
940 destinations and set pair->dst_parent_abspath and pair->base_name for each
941 item to the resulting location if they do */
942 static svn_error_t *
verify_wc_dsts(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,svn_boolean_t is_move,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)943 verify_wc_dsts(const apr_array_header_t *copy_pairs,
944 svn_boolean_t make_parents,
945 svn_boolean_t is_move,
946 svn_boolean_t metadata_only,
947 svn_client_ctx_t *ctx,
948 apr_pool_t *result_pool,
949 apr_pool_t *scratch_pool)
950 {
951 int i;
952 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
953
954 /* Check that DST does not exist, but its parent does */
955 for (i = 0; i < copy_pairs->nelts; i++)
956 {
957 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
958 svn_client__copy_pair_t *);
959 svn_node_kind_t dst_kind, dst_parent_kind;
960
961 svn_pool_clear(iterpool);
962
963 /* If DST_PATH does not exist, then its basename will become a new
964 file or dir added to its parent (possibly an implicit '.').
965 Else, just error out. */
966 SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
967 pair->dst_abspath_or_url,
968 FALSE /* show_deleted */,
969 TRUE /* show_hidden */,
970 iterpool));
971 if (dst_kind != svn_node_none)
972 {
973 svn_boolean_t is_excluded;
974 svn_boolean_t is_server_excluded;
975
976 SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
977 &is_server_excluded, ctx->wc_ctx,
978 pair->dst_abspath_or_url, FALSE,
979 iterpool));
980
981 if (is_excluded || is_server_excluded)
982 {
983 return svn_error_createf(
984 SVN_ERR_WC_OBSTRUCTED_UPDATE,
985 NULL, _("Path '%s' exists, but is excluded"),
986 svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
987 }
988 else
989 return svn_error_createf(
990 SVN_ERR_ENTRY_EXISTS, NULL,
991 _("Path '%s' already exists"),
992 svn_dirent_local_style(pair->dst_abspath_or_url,
993 scratch_pool));
994 }
995
996 /* Check that there is no unversioned obstruction */
997 if (metadata_only)
998 dst_kind = svn_node_none;
999 else
1000 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1001 iterpool));
1002
1003 if (dst_kind != svn_node_none)
1004 {
1005 if (is_move
1006 && copy_pairs->nelts == 1
1007 && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
1008 svn_dirent_dirname(pair->dst_abspath_or_url,
1009 iterpool)) == 0)
1010 {
1011 const char *dst;
1012 char *dst_apr;
1013 apr_status_t apr_err;
1014 /* We have a rename inside a directory, which might collide
1015 just because the case insensivity of the filesystem makes
1016 the source match the destination. */
1017
1018 SVN_ERR(svn_path_cstring_from_utf8(&dst,
1019 pair->dst_abspath_or_url,
1020 scratch_pool));
1021
1022 apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
1023 APR_FILEPATH_TRUENAME, iterpool);
1024
1025 if (!apr_err)
1026 {
1027 /* And now bring it back to our canonical format */
1028 SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
1029 dst = svn_dirent_canonicalize(dst, iterpool);
1030 }
1031 /* else: Don't report this error; just report the normal error */
1032
1033 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
1034 {
1035 /* Ok, we have a single case only rename. Get out of here */
1036 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1037 pair->dst_abspath_or_url, result_pool);
1038
1039 svn_pool_destroy(iterpool);
1040 return SVN_NO_ERROR;
1041 }
1042 }
1043
1044 return svn_error_createf(
1045 SVN_ERR_ENTRY_EXISTS, NULL,
1046 _("Path '%s' already exists as unversioned node"),
1047 svn_dirent_local_style(pair->dst_abspath_or_url,
1048 scratch_pool));
1049 }
1050
1051 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1052 pair->dst_abspath_or_url, result_pool);
1053
1054 /* Make sure the destination parent is a directory and produce a clear
1055 error message if it is not. */
1056 SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
1057 ctx->wc_ctx, pair->dst_parent_abspath,
1058 FALSE, TRUE,
1059 iterpool));
1060 if (dst_parent_kind == svn_node_none)
1061 {
1062 if (make_parents)
1063 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1064 TRUE, ctx, iterpool));
1065 else
1066 {
1067 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1068 &dst_parent_kind, scratch_pool));
1069 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1070 (dst_parent_kind == svn_node_dir)
1071 ? _("Directory '%s' is not under "
1072 "version control")
1073 : _("Path '%s' is not a directory"),
1074 svn_dirent_local_style(
1075 pair->dst_parent_abspath,
1076 scratch_pool));
1077 }
1078 }
1079 else if (dst_parent_kind != svn_node_dir)
1080 {
1081 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1082 _("Path '%s' is not a directory"),
1083 svn_dirent_local_style(
1084 pair->dst_parent_abspath, scratch_pool));
1085 }
1086
1087 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1088 &dst_parent_kind, scratch_pool));
1089
1090 if (dst_parent_kind != svn_node_dir)
1091 return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
1092 _("Path '%s' is not a directory"),
1093 svn_dirent_local_style(
1094 pair->dst_parent_abspath, scratch_pool));
1095 }
1096
1097 svn_pool_destroy(iterpool);
1098
1099 return SVN_NO_ERROR;
1100 }
1101
1102 /* Verify that the WC sources in COPY_PAIRS exist, and set pair->src_kind
1103 for each.
1104 */
1105 static svn_error_t *
verify_wc_srcs(const apr_array_header_t * copy_pairs,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1106 verify_wc_srcs(const apr_array_header_t *copy_pairs,
1107 svn_client_ctx_t *ctx,
1108 apr_pool_t *scratch_pool)
1109 {
1110 int i;
1111 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1112
1113 /* Check that all of our SRCs exist. */
1114 for (i = 0; i < copy_pairs->nelts; i++)
1115 {
1116 svn_boolean_t deleted_ok;
1117 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1118 svn_client__copy_pair_t *);
1119 svn_pool_clear(iterpool);
1120
1121 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1122 || pair->src_op_revision.kind == svn_opt_revision_base);
1123
1124 /* Verify that SRC_PATH exists. */
1125 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1126 pair->src_abspath_or_url,
1127 deleted_ok, FALSE, iterpool));
1128 if (pair->src_kind == svn_node_none)
1129 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1130 _("Path '%s' does not exist"),
1131 svn_dirent_local_style(
1132 pair->src_abspath_or_url,
1133 scratch_pool));
1134 }
1135 svn_pool_destroy(iterpool);
1136
1137 return SVN_NO_ERROR;
1138 }
1139
1140
1141 /* Path-specific state used as part of path_driver_cb_baton. */
1142 typedef struct path_driver_info_t
1143 {
1144 const char *src_url;
1145 const char *src_path;
1146 const char *dst_path;
1147 svn_node_kind_t src_kind;
1148 svn_revnum_t src_revnum;
1149 svn_boolean_t resurrection;
1150 svn_boolean_t dir_add;
1151 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
1152 svn_string_t *externals; /* new externals definitions for the target */
1153 svn_boolean_t only_pin_externals;
1154 } path_driver_info_t;
1155
1156
1157 /* The baton used with the path_driver_cb_func() callback for a copy
1158 or move operation. */
1159 struct path_driver_cb_baton
1160 {
1161 /* A hash of path -> path_driver_info_t *'s. */
1162 apr_hash_t *action_hash;
1163
1164 /* Whether the operation is a move or copy. */
1165 svn_boolean_t is_move;
1166 };
1167
1168 static svn_error_t *
path_driver_cb_func(void ** dir_baton,const svn_delta_editor_t * editor,void * edit_baton,void * parent_baton,void * callback_baton,const char * path,apr_pool_t * pool)1169 path_driver_cb_func(void **dir_baton,
1170 const svn_delta_editor_t *editor,
1171 void *edit_baton,
1172 void *parent_baton,
1173 void *callback_baton,
1174 const char *path,
1175 apr_pool_t *pool)
1176 {
1177 struct path_driver_cb_baton *cb_baton = callback_baton;
1178 svn_boolean_t do_delete = FALSE, do_add = FALSE;
1179 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1180
1181 /* Initialize return value. */
1182 *dir_baton = NULL;
1183
1184 /* This function should never get an empty PATH. We can neither
1185 create nor delete the empty PATH, so if someone is calling us
1186 with such, the code is just plain wrong. */
1187 SVN_ERR_ASSERT(! svn_path_is_empty(path));
1188
1189 /* Check to see if we need to add the path as a parent directory. */
1190 if (path_info->dir_add)
1191 {
1192 return editor->add_directory(path, parent_baton, NULL,
1193 SVN_INVALID_REVNUM, pool,
1194 dir_baton);
1195 }
1196
1197 /* If this is a resurrection, we know the source and dest paths are
1198 the same, and that our driver will only be calling us once. */
1199 if (path_info->resurrection)
1200 {
1201 /* If this is a move, we do nothing. Otherwise, we do the copy. */
1202 if (! cb_baton->is_move)
1203 do_add = TRUE;
1204 }
1205 /* Not a resurrection. */
1206 else
1207 {
1208 /* If this is a move, we check PATH to see if it is the source
1209 or the destination of the move. */
1210 if (cb_baton->is_move)
1211 {
1212 if (strcmp(path_info->src_path, path) == 0)
1213 do_delete = TRUE;
1214 else
1215 do_add = TRUE;
1216 }
1217 /* Not a move? This must just be the copy addition. */
1218 else
1219 {
1220 do_add = !path_info->only_pin_externals;
1221 }
1222 }
1223
1224 if (do_delete)
1225 {
1226 SVN_ERR(editor->delete_entry(path, SVN_INVALID_REVNUM,
1227 parent_baton, pool));
1228 }
1229 if (do_add)
1230 {
1231 SVN_ERR(svn_path_check_valid(path, pool));
1232
1233 if (path_info->src_kind == svn_node_file)
1234 {
1235 void *file_baton;
1236 SVN_ERR(editor->add_file(path, parent_baton,
1237 path_info->src_url,
1238 path_info->src_revnum,
1239 pool, &file_baton));
1240 if (path_info->mergeinfo)
1241 SVN_ERR(editor->change_file_prop(file_baton,
1242 SVN_PROP_MERGEINFO,
1243 path_info->mergeinfo,
1244 pool));
1245 SVN_ERR(editor->close_file(file_baton, NULL, pool));
1246 }
1247 else
1248 {
1249 SVN_ERR(editor->add_directory(path, parent_baton,
1250 path_info->src_url,
1251 path_info->src_revnum,
1252 pool, dir_baton));
1253 if (path_info->mergeinfo)
1254 SVN_ERR(editor->change_dir_prop(*dir_baton,
1255 SVN_PROP_MERGEINFO,
1256 path_info->mergeinfo,
1257 pool));
1258 }
1259 }
1260
1261 if (path_info->externals)
1262 {
1263 if (*dir_baton == NULL)
1264 SVN_ERR(editor->open_directory(path, parent_baton,
1265 SVN_INVALID_REVNUM,
1266 pool, dir_baton));
1267
1268 SVN_ERR(editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1269 path_info->externals, pool));
1270 }
1271
1272 return SVN_NO_ERROR;
1273 }
1274
1275
1276 /* Starting with the path DIR relative to the RA_SESSION's session
1277 URL, work up through DIR's parents until an existing node is found.
1278 Push each nonexistent path onto the array NEW_DIRS, allocating in
1279 POOL. Raise an error if the existing node is not a directory.
1280
1281 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1282 ### implementation susceptible to race conditions. */
1283 static svn_error_t *
find_absent_parents1(svn_ra_session_t * ra_session,const char * dir,apr_array_header_t * new_dirs,apr_pool_t * pool)1284 find_absent_parents1(svn_ra_session_t *ra_session,
1285 const char *dir,
1286 apr_array_header_t *new_dirs,
1287 apr_pool_t *pool)
1288 {
1289 svn_node_kind_t kind;
1290 apr_pool_t *iterpool = svn_pool_create(pool);
1291
1292 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1293 iterpool));
1294
1295 while (kind == svn_node_none)
1296 {
1297 svn_pool_clear(iterpool);
1298
1299 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1300 dir = svn_dirent_dirname(dir, pool);
1301
1302 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1303 &kind, iterpool));
1304 }
1305
1306 if (kind != svn_node_dir)
1307 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1308 _("Path '%s' already exists, but is not a "
1309 "directory"), dir);
1310
1311 svn_pool_destroy(iterpool);
1312 return SVN_NO_ERROR;
1313 }
1314
1315 /* Starting with the URL *TOP_DST_URL which is also the root of
1316 RA_SESSION, work up through its parents until an existing node is
1317 found. Push each nonexistent URL onto the array NEW_DIRS,
1318 allocating in POOL. Raise an error if the existing node is not a
1319 directory.
1320
1321 Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1322
1323 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1324 ### implementation susceptible to race conditions. */
1325 static svn_error_t *
find_absent_parents2(svn_ra_session_t * ra_session,const char ** top_dst_url,apr_array_header_t * new_dirs,apr_pool_t * pool)1326 find_absent_parents2(svn_ra_session_t *ra_session,
1327 const char **top_dst_url,
1328 apr_array_header_t *new_dirs,
1329 apr_pool_t *pool)
1330 {
1331 const char *root_url = *top_dst_url;
1332 svn_node_kind_t kind;
1333
1334 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1335 pool));
1336
1337 while (kind == svn_node_none)
1338 {
1339 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1340 root_url = svn_uri_dirname(root_url, pool);
1341
1342 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1343 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1344 pool));
1345 }
1346
1347 if (kind != svn_node_dir)
1348 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1349 _("Path '%s' already exists, but is not a directory"),
1350 root_url);
1351
1352 *top_dst_url = root_url;
1353 return SVN_NO_ERROR;
1354 }
1355
1356 /* Queue property changes for pinning svn:externals properties set on
1357 * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1358 * is keyed by the relative path of each descendant which should have some
1359 * or all of its externals pinned, with the corresponding pinned svn:externals
1360 * properties as values. Property changes are queued in a new list of path
1361 * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1362 * existing item is found for the descendant. Allocate results in RESULT_POOL.
1363 * Use SCRATCH_POOL for temporary allocations. */
1364 static svn_error_t *
queue_externals_change_path_infos(apr_array_header_t * new_path_infos,apr_array_header_t * path_infos,apr_hash_t * pinned_externals,path_driver_info_t * parent_info,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1365 queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1366 apr_array_header_t *path_infos,
1367 apr_hash_t *pinned_externals,
1368 path_driver_info_t *parent_info,
1369 apr_pool_t *result_pool,
1370 apr_pool_t *scratch_pool)
1371 {
1372 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1373 apr_hash_index_t *hi;
1374
1375 for (hi = apr_hash_first(scratch_pool, pinned_externals);
1376 hi;
1377 hi = apr_hash_next(hi))
1378 {
1379 const char *dst_relpath = apr_hash_this_key(hi);
1380 svn_string_t *externals_prop = apr_hash_this_val(hi);
1381 const char *src_url;
1382 path_driver_info_t *info;
1383 int i;
1384
1385 svn_pool_clear(iterpool);
1386
1387 src_url = svn_path_url_add_component2(parent_info->src_url,
1388 dst_relpath, iterpool);
1389
1390 /* Try to find a path info the external change can be applied to. */
1391 info = NULL;
1392 for (i = 0; i < path_infos->nelts; i++)
1393 {
1394 path_driver_info_t *existing_info;
1395
1396 existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1397 if (strcmp(src_url, existing_info->src_url) == 0)
1398 {
1399 info = existing_info;
1400 break;
1401 }
1402 }
1403
1404 if (info == NULL)
1405 {
1406 /* A copied-along child needs its externals pinned.
1407 Create a new path info for this property change. */
1408 info = apr_pcalloc(result_pool, sizeof(*info));
1409 info->src_url = svn_path_url_add_component2(
1410 parent_info->src_url, dst_relpath,
1411 result_pool);
1412 info->src_path = NULL; /* Only needed on copied dirs */
1413 info->dst_path = svn_relpath_join(parent_info->dst_path,
1414 dst_relpath,
1415 result_pool);
1416 info->src_kind = svn_node_dir;
1417 info->only_pin_externals = TRUE;
1418 APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1419 }
1420
1421 info->externals = externals_prop;
1422 }
1423
1424 svn_pool_destroy(iterpool);
1425
1426 return SVN_NO_ERROR;
1427 }
1428
1429 static svn_error_t *
repos_to_repos_copy(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,svn_boolean_t is_move,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,apr_pool_t * pool)1430 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
1431 svn_boolean_t make_parents,
1432 const apr_hash_t *revprop_table,
1433 svn_commit_callback2_t commit_callback,
1434 void *commit_baton,
1435 svn_client_ctx_t *ctx,
1436 svn_boolean_t is_move,
1437 svn_boolean_t pin_externals,
1438 const apr_hash_t *externals_to_pin,
1439 apr_pool_t *pool)
1440 {
1441 svn_error_t *err;
1442 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1443 sizeof(const char *));
1444 apr_hash_t *action_hash = apr_hash_make(pool);
1445 apr_array_header_t *path_infos;
1446 const char *top_url, *top_url_all, *top_url_dst;
1447 const char *message, *repos_root;
1448 svn_ra_session_t *ra_session = NULL;
1449 const svn_delta_editor_t *editor;
1450 void *edit_baton;
1451 struct path_driver_cb_baton cb_baton;
1452 apr_array_header_t *new_dirs = NULL;
1453 apr_hash_t *commit_revprops;
1454 apr_array_header_t *pin_externals_only_infos = NULL;
1455 int i;
1456 svn_client__copy_pair_t *first_pair =
1457 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1458
1459 /* Open an RA session to the first copy pair's destination. We'll
1460 be verifying that every one of our copy source and destination
1461 URLs is or is beneath this sucker's repository root URL as a form
1462 of a cheap(ish) sanity check. */
1463 SVN_ERR(svn_client_open_ra_session2(&ra_session,
1464 first_pair->src_abspath_or_url, NULL,
1465 ctx, pool, pool));
1466 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1467
1468 /* Verify that sources and destinations are all at or under
1469 REPOS_ROOT. While here, create a path_info struct for each
1470 src/dst pair and initialize portions of it with normalized source
1471 location information. */
1472 path_infos = apr_array_make(pool, copy_pairs->nelts,
1473 sizeof(path_driver_info_t *));
1474 for (i = 0; i < copy_pairs->nelts; i++)
1475 {
1476 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1477 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1478 svn_client__copy_pair_t *);
1479 apr_hash_t *mergeinfo;
1480
1481 /* Are the source and destination URLs at or under REPOS_ROOT? */
1482 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1483 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1484 return svn_error_create
1485 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1486 _("Source and destination URLs appear not to point to the "
1487 "same repository."));
1488
1489 /* Run the history function to get the source's URL and revnum in the
1490 operational revision. */
1491 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1492 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1493 &pair->src_revnum,
1494 NULL, NULL,
1495 ra_session,
1496 pair->src_abspath_or_url,
1497 &pair->src_peg_revision,
1498 &pair->src_op_revision, NULL,
1499 ctx, pool));
1500
1501 /* Go ahead and grab mergeinfo from the source, too. */
1502 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1503 SVN_ERR(svn_client__get_repos_mergeinfo(
1504 &mergeinfo, ra_session,
1505 pair->src_abspath_or_url, pair->src_revnum,
1506 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1507 if (mergeinfo)
1508 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1509
1510 /* Plop an INFO structure onto our array thereof. */
1511 info->src_url = pair->src_abspath_or_url;
1512 info->src_revnum = pair->src_revnum;
1513 info->resurrection = FALSE;
1514 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1515 }
1516
1517 /* If this is a move, we have to open our session to the longest
1518 path common to all SRC_URLS and DST_URLS in the repository so we
1519 can do existence checks on all paths, and so we can operate on
1520 all paths in the case of a move. But if this is *not* a move,
1521 then opening our session at the longest path common to sources
1522 *and* destinations might be an optimization when the user is
1523 authorized to access all that stuff, but could cause the
1524 operation to fail altogether otherwise. See issue #3242. */
1525 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1526 pool));
1527 top_url = is_move ? top_url_all : top_url_dst;
1528
1529 /* Check each src/dst pair for resurrection, and verify that TOP_URL
1530 is anchored high enough to cover all the editor_t activities
1531 required for this operation. */
1532 for (i = 0; i < copy_pairs->nelts; i++)
1533 {
1534 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1535 svn_client__copy_pair_t *);
1536 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1537 path_driver_info_t *);
1538
1539 /* Source and destination are the same? It's a resurrection. */
1540 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1541 info->resurrection = TRUE;
1542
1543 /* We need to add each dst_URL, and (in a move) we'll need to
1544 delete each src_URL. Our selection of TOP_URL so far ensures
1545 that all our destination URLs (and source URLs, for moves)
1546 are at least as deep as TOP_URL, but we need to make sure
1547 that TOP_URL is an *ancestor* of all our to-be-edited paths.
1548
1549 Issue #683 is demonstrates this scenario. If you're
1550 resurrecting a deleted item like this: 'svn cp -rN src_URL
1551 dst_URL', then src_URL == dst_URL == top_url. In this
1552 situation, we want to open an RA session to be at least the
1553 *parent* of all three. */
1554 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1555 && (strcmp(top_url, repos_root) != 0))
1556 {
1557 top_url = svn_uri_dirname(top_url, pool);
1558 }
1559 if (is_move
1560 && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1561 && (strcmp(top_url, repos_root) != 0))
1562 {
1563 top_url = svn_uri_dirname(top_url, pool);
1564 }
1565 }
1566
1567 /* Point the RA session to our current TOP_URL. */
1568 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1569
1570 /* If we're allowed to create nonexistent parent directories of our
1571 destinations, then make a list in NEW_DIRS of the parent
1572 directories of the destination that don't yet exist. */
1573 if (make_parents)
1574 {
1575 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1576
1577 /* If this is a move, TOP_URL is at least the common ancestor of
1578 all the paths (sources and destinations) involved. Assuming
1579 the sources exist (which is fair, because if they don't, this
1580 whole operation will fail anyway), TOP_URL must also exist.
1581 So it's the paths between TOP_URL and the destinations which
1582 we have to check for existence. But here, we take advantage
1583 of the knowledge of our caller. We know that if there are
1584 multiple copy/move operations being requested, then the
1585 destinations of the copies/moves will all be siblings of one
1586 another. Therefore, we need only to check for the
1587 nonexistent paths between TOP_URL and *one* of our
1588 destinations to find nonexistent parents of all of them. */
1589 if (is_move)
1590 {
1591 /* Imagine a situation where the user tries to copy an
1592 existing source directory to nonexistent directory with
1593 --parents options specified:
1594
1595 svn copy --parents URL/src URL/dst
1596
1597 where src exists and dst does not. If the dirname of the
1598 destination path is equal to TOP_URL,
1599 do not try to add dst to the NEW_DIRS list since it
1600 will be added to the commit items array later in this
1601 function. */
1602 const char *dir = svn_uri_skip_ancestor(
1603 top_url,
1604 svn_uri_dirname(first_pair->dst_abspath_or_url,
1605 pool),
1606 pool);
1607 if (dir && *dir)
1608 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1609 }
1610 /* If, however, this is *not* a move, TOP_URL only points to the
1611 common ancestor of our destination path(s), or possibly one
1612 level higher. We'll need to do an existence crawl toward the
1613 root of the repository, starting with one of our destinations
1614 (see "... take advantage of the knowledge of our caller ..."
1615 above), and possibly adjusting TOP_URL as we go. */
1616 else
1617 {
1618 apr_array_header_t *new_urls =
1619 apr_array_make(pool, 0, sizeof(const char *));
1620 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1621
1622 /* Convert absolute URLs into relpaths relative to TOP_URL. */
1623 for (i = 0; i < new_urls->nelts; i++)
1624 {
1625 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1626 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1627
1628 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1629 }
1630 }
1631 }
1632
1633 /* For each src/dst pair, check to see if that SRC_URL is a child of
1634 the DST_URL (excepting the case where DST_URL is the repo root).
1635 If it is, and the parent of DST_URL is the current TOP_URL, then we
1636 need to reparent the session one directory higher, the parent of
1637 the DST_URL. */
1638 for (i = 0; i < copy_pairs->nelts; i++)
1639 {
1640 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1641 svn_client__copy_pair_t *);
1642 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1643 path_driver_info_t *);
1644 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1645 pair->src_abspath_or_url,
1646 pool);
1647
1648 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1649 && (relpath != NULL && *relpath != '\0'))
1650 {
1651 info->resurrection = TRUE;
1652 top_url = svn_uri_get_longest_ancestor(
1653 top_url,
1654 svn_uri_dirname(pair->dst_abspath_or_url, pool),
1655 pool);
1656 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1657 }
1658 }
1659
1660 /* Get the portions of the SRC and DST URLs that are relative to
1661 TOP_URL (URI-decoding them while we're at it), verify that the
1662 source exists and the proposed destination does not, and toss
1663 what we've learned into the INFO array. (For copies -- that is,
1664 non-moves -- the relative source URL NULL because it isn't a
1665 child of the TOP_URL at all. That's okay, we'll deal with
1666 it.) */
1667 for (i = 0; i < copy_pairs->nelts; i++)
1668 {
1669 svn_client__copy_pair_t *pair =
1670 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1671 path_driver_info_t *info =
1672 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1673 svn_node_kind_t dst_kind;
1674 const char *src_rel, *dst_rel;
1675
1676 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1677 if (src_rel)
1678 {
1679 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1680 &info->src_kind, pool));
1681 }
1682 else
1683 {
1684 const char *old_url;
1685
1686 src_rel = NULL;
1687 SVN_ERR_ASSERT(! is_move);
1688
1689 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1690 pair->src_abspath_or_url,
1691 pool));
1692 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1693 &info->src_kind, pool));
1694 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1695 }
1696 if (info->src_kind == svn_node_none)
1697 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1698 _("Path '%s' does not exist in revision %ld"),
1699 pair->src_abspath_or_url, pair->src_revnum);
1700
1701 /* Figure out the basename that will result from this operation,
1702 and ensure that we aren't trying to overwrite existing paths. */
1703 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1704 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1705 &dst_kind, pool));
1706 if (dst_kind != svn_node_none)
1707 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1708 _("Path '%s' already exists"),
1709 pair->dst_abspath_or_url);
1710
1711 /* More info for our INFO structure. */
1712 info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1713 info->dst_path = dst_rel;
1714
1715 svn_hash_sets(action_hash, info->dst_path, info);
1716 if (is_move && (! info->resurrection))
1717 svn_hash_sets(action_hash, info->src_path, info);
1718
1719 if (pin_externals)
1720 {
1721 apr_hash_t *pinned_externals;
1722
1723 SVN_ERR(resolve_pinned_externals(&pinned_externals,
1724 externals_to_pin, pair,
1725 ra_session, repos_root,
1726 ctx, pool, pool));
1727 if (pin_externals_only_infos == NULL)
1728 {
1729 pin_externals_only_infos =
1730 apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1731 }
1732 SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1733 path_infos,
1734 pinned_externals,
1735 info, pool, pool));
1736 }
1737 }
1738
1739 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1740 {
1741 /* Produce a list of new paths to add, and provide it to the
1742 mechanism used to acquire a log message. */
1743 svn_client_commit_item3_t *item;
1744 const char *tmp_file;
1745 apr_array_header_t *commit_items
1746 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1747
1748 /* Add any intermediate directories to the message */
1749 if (make_parents)
1750 {
1751 for (i = 0; i < new_dirs->nelts; i++)
1752 {
1753 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1754
1755 item = svn_client_commit_item3_create(pool);
1756 item->url = svn_path_url_add_component2(top_url, relpath, pool);
1757 item->kind = svn_node_dir;
1758 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1759 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1760 }
1761 }
1762
1763 for (i = 0; i < path_infos->nelts; i++)
1764 {
1765 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1766 path_driver_info_t *);
1767
1768 item = svn_client_commit_item3_create(pool);
1769 item->url = svn_path_url_add_component2(top_url, info->dst_path,
1770 pool);
1771 item->kind = info->src_kind;
1772 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1773 | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1774 item->copyfrom_url = info->src_url;
1775 item->copyfrom_rev = info->src_revnum;
1776 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1777
1778 if (is_move && (! info->resurrection))
1779 {
1780 item = svn_client_commit_item3_create(pool);
1781 item->url = svn_path_url_add_component2(top_url, info->src_path,
1782 pool);
1783 item->kind = info->src_kind;
1784 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1785 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1786 }
1787 }
1788
1789 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1790 ctx, pool));
1791 if (! message)
1792 return SVN_NO_ERROR;
1793 }
1794 else
1795 message = "";
1796
1797 /* Setup our PATHS for the path-based editor drive. */
1798 /* First any intermediate directories. */
1799 if (make_parents)
1800 {
1801 for (i = 0; i < new_dirs->nelts; i++)
1802 {
1803 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1804 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1805
1806 info->dst_path = relpath;
1807 info->dir_add = TRUE;
1808
1809 APR_ARRAY_PUSH(paths, const char *) = relpath;
1810 svn_hash_sets(action_hash, relpath, info);
1811 }
1812 }
1813
1814 /* Then our copy destinations and move sources (if any). */
1815 for (i = 0; i < path_infos->nelts; i++)
1816 {
1817 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1818 path_driver_info_t *);
1819
1820 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1821 if (is_move && (! info->resurrection))
1822 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1823 }
1824
1825 /* Add any items which only need their externals pinned. */
1826 if (pin_externals_only_infos)
1827 {
1828 for (i = 0; i < pin_externals_only_infos->nelts; i++)
1829 {
1830 path_driver_info_t *info;
1831
1832 info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1833 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1834 svn_hash_sets(action_hash, info->dst_path, info);
1835 }
1836 }
1837
1838 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1839 message, ctx, pool));
1840
1841 /* Fetch RA commit editor. */
1842 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1843 svn_client__get_shim_callbacks(ctx->wc_ctx,
1844 NULL, pool)));
1845 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1846 commit_revprops,
1847 commit_callback,
1848 commit_baton,
1849 NULL, TRUE, /* No lock tokens */
1850 pool));
1851
1852 /* Setup the callback baton. */
1853 cb_baton.action_hash = action_hash;
1854 cb_baton.is_move = is_move;
1855
1856 /* Call the path-based editor driver. */
1857 err = svn_delta_path_driver3(editor, edit_baton, paths, TRUE,
1858 path_driver_cb_func, &cb_baton, pool);
1859 if (err)
1860 {
1861 /* At least try to abort the edit (and fs txn) before throwing err. */
1862 return svn_error_compose_create(
1863 err,
1864 editor->abort_edit(edit_baton, pool));
1865 }
1866
1867 if (ctx->notify_func2)
1868 {
1869 svn_wc_notify_t *notify;
1870 notify = svn_wc_create_notify_url(top_url,
1871 svn_wc_notify_commit_finalizing,
1872 pool);
1873 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1874 }
1875
1876 /* Close the edit. */
1877 return svn_error_trace(editor->close_edit(edit_baton, pool));
1878 }
1879
1880 /* Baton for check_url_kind */
1881 struct check_url_kind_baton
1882 {
1883 svn_ra_session_t *session;
1884 const char *repos_root_url;
1885 svn_boolean_t should_reparent;
1886 };
1887
1888 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1889 static svn_error_t *
check_url_kind(void * baton,svn_node_kind_t * kind,const char * url,svn_revnum_t revision,apr_pool_t * scratch_pool)1890 check_url_kind(void *baton,
1891 svn_node_kind_t *kind,
1892 const char *url,
1893 svn_revnum_t revision,
1894 apr_pool_t *scratch_pool)
1895 {
1896 struct check_url_kind_baton *cukb = baton;
1897
1898 /* If we don't have a session or can't use the session, get one */
1899 if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1900 *kind = svn_node_none;
1901 else
1902 {
1903 cukb->should_reparent = TRUE;
1904
1905 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1906
1907 SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1908 kind, scratch_pool));
1909 }
1910
1911 return SVN_NO_ERROR;
1912 }
1913
1914 /* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1915 * in the COMMIT_ITEMS list.
1916 * If the list does not already have a commit item for COMMIT_URL
1917 * add a new commit item for the property change.
1918 * Allocate results in RESULT_POOL.
1919 * Use SCRATCH_POOL for temporary allocations. */
1920 static svn_error_t *
queue_prop_change_commit_items(const char * local_abspath,const char * commit_url,apr_array_header_t * commit_items,const char * propname,svn_string_t * propval,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1921 queue_prop_change_commit_items(const char *local_abspath,
1922 const char *commit_url,
1923 apr_array_header_t *commit_items,
1924 const char *propname,
1925 svn_string_t *propval,
1926 apr_pool_t *result_pool,
1927 apr_pool_t *scratch_pool)
1928 {
1929 svn_client_commit_item3_t *item = NULL;
1930 svn_prop_t *prop;
1931 int i;
1932
1933 for (i = 0; i < commit_items->nelts; i++)
1934 {
1935 svn_client_commit_item3_t *existing_item;
1936
1937 existing_item = APR_ARRAY_IDX(commit_items, i,
1938 svn_client_commit_item3_t *);
1939 if (strcmp(existing_item->url, commit_url) == 0)
1940 {
1941 item = existing_item;
1942 break;
1943 }
1944 }
1945
1946 if (item == NULL)
1947 {
1948 item = svn_client_commit_item3_create(result_pool);
1949 item->path = local_abspath;
1950 item->url = commit_url;
1951 item->kind = svn_node_dir;
1952 item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1953
1954 item->incoming_prop_changes = apr_array_make(result_pool, 1,
1955 sizeof(svn_prop_t *));
1956 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1957 }
1958 else
1959 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1960
1961 if (item->outgoing_prop_changes == NULL)
1962 item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1963 sizeof(svn_prop_t *));
1964
1965 prop = apr_palloc(result_pool, sizeof(*prop));
1966 prop->name = propname;
1967 prop->value = propval;
1968 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1969
1970 return SVN_NO_ERROR;
1971 }
1972
1973 /* ### Copy ...
1974 * COMMIT_INFO_P is ...
1975 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1976 * and each 'dst_abspath_or_url' is a URL.
1977 * MAKE_PARENTS is ...
1978 * REVPROP_TABLE is ...
1979 * CTX is ... */
1980 static svn_error_t *
wc_to_repos_copy(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1981 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1982 svn_boolean_t make_parents,
1983 const apr_hash_t *revprop_table,
1984 svn_commit_callback2_t commit_callback,
1985 void *commit_baton,
1986 svn_boolean_t pin_externals,
1987 const apr_hash_t *externals_to_pin,
1988 svn_client_ctx_t *ctx,
1989 apr_pool_t *scratch_pool)
1990 {
1991 const char *message;
1992 const char *top_src_path, *top_dst_url;
1993 struct check_url_kind_baton cukb;
1994 const char *top_src_abspath;
1995 svn_ra_session_t *ra_session;
1996 const svn_delta_editor_t *editor;
1997 #ifdef ENABLE_EV2_SHIMS
1998 apr_hash_t *relpath_map = NULL;
1999 #endif
2000 void *edit_baton;
2001 svn_client__committables_t *committables;
2002 apr_array_header_t *commit_items;
2003 apr_pool_t *iterpool;
2004 apr_array_header_t *new_dirs = NULL;
2005 apr_hash_t *commit_revprops;
2006 svn_client__copy_pair_t *first_pair;
2007 apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2008 apr_array_header_t *commit_items_for_dav;
2009 int i;
2010
2011 /* Find the common root of all the source paths */
2012 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2013 scratch_pool));
2014
2015 /* Do we need to lock the working copy? 1.6 didn't take a write
2016 lock, but what happens if the working copy changes during the copy
2017 operation? */
2018
2019 iterpool = svn_pool_create(scratch_pool);
2020
2021 /* Determine the longest common ancestor for the destinations, and open an RA
2022 session to that location. */
2023 /* ### But why start by getting the _parent_ of the first one? */
2024 /* --- That works because multiple destinations always point to the same
2025 * directory. I'm rather wondering why we need to find a common
2026 * destination parent here at all, instead of simply getting
2027 * top_dst_url from get_copy_pair_ancestors() above?
2028 * It looks like the entire block of code hanging off this comment
2029 * is redundant. */
2030 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2031 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2032 for (i = 1; i < copy_pairs->nelts; i++)
2033 {
2034 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2035 svn_client__copy_pair_t *);
2036 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2037 pair->dst_abspath_or_url,
2038 scratch_pool);
2039 }
2040
2041 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2042
2043 commit_items_for_dav = apr_array_make(session_pool, 0,
2044 sizeof(svn_client_commit_item3_t*));
2045
2046 /* Open a session to help while determining the exact targets */
2047 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2048 top_src_abspath,
2049 commit_items_for_dav,
2050 FALSE /* write_dav_props */,
2051 TRUE /* read_dav_props */,
2052 ctx,
2053 session_pool, session_pool));
2054
2055 /* If requested, determine the nearest existing parent of the destination,
2056 and reparent the ra session there. */
2057 if (make_parents)
2058 {
2059 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2060 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2061 scratch_pool));
2062 }
2063
2064 /* Figure out the basename that will result from each copy and check to make
2065 sure it doesn't exist already. */
2066 for (i = 0; i < copy_pairs->nelts; i++)
2067 {
2068 svn_node_kind_t dst_kind;
2069 const char *dst_rel;
2070 svn_client__copy_pair_t *pair =
2071 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2072
2073 svn_pool_clear(iterpool);
2074 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2075 iterpool);
2076 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2077 &dst_kind, iterpool));
2078 if (dst_kind != svn_node_none)
2079 {
2080 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2081 _("Path '%s' already exists"),
2082 pair->dst_abspath_or_url);
2083 }
2084 }
2085
2086 cukb.session = ra_session;
2087 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2088 cukb.should_reparent = FALSE;
2089
2090 /* Crawl the working copy for commit items. */
2091 /* ### TODO: Pass check_url_func for issue #3314 handling */
2092 SVN_ERR(svn_client__get_copy_committables(&committables,
2093 copy_pairs,
2094 check_url_kind, &cukb,
2095 ctx, scratch_pool, iterpool));
2096
2097 /* The committables are keyed by the repository root */
2098 commit_items = svn_hash_gets(committables->by_repository,
2099 cukb.repos_root_url);
2100 SVN_ERR_ASSERT(commit_items != NULL);
2101
2102 if (cukb.should_reparent)
2103 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2104
2105 /* If we are creating intermediate directories, tack them onto the list
2106 of committables. */
2107 if (make_parents)
2108 {
2109 for (i = 0; i < new_dirs->nelts; i++)
2110 {
2111 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2112 svn_client_commit_item3_t *item;
2113
2114 item = svn_client_commit_item3_create(scratch_pool);
2115 item->url = url;
2116 item->kind = svn_node_dir;
2117 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2118 item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2119 sizeof(svn_prop_t *));
2120 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2121 }
2122 }
2123
2124 /* ### TODO: This extra loop would be unnecessary if this code lived
2125 ### in svn_client__get_copy_committables(), which is incidentally
2126 ### only used above (so should really be in this source file). */
2127 for (i = 0; i < copy_pairs->nelts; i++)
2128 {
2129 apr_hash_t *mergeinfo, *wc_mergeinfo;
2130 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2131 svn_client__copy_pair_t *);
2132 svn_client_commit_item3_t *item =
2133 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2134 svn_client__pathrev_t *src_origin;
2135
2136 svn_pool_clear(iterpool);
2137
2138 SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2139 pair->src_abspath_or_url,
2140 ctx, iterpool, iterpool));
2141
2142 /* Set the mergeinfo for the destination to the combined merge
2143 info known to the WC and the repository. */
2144 /* Repository mergeinfo (or NULL if it's locally added)... */
2145 if (src_origin)
2146 SVN_ERR(svn_client__get_repos_mergeinfo(
2147 &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2148 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2149 else
2150 mergeinfo = NULL;
2151 /* ... and WC mergeinfo. */
2152 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2153 pair->src_abspath_or_url,
2154 iterpool, iterpool));
2155 if (wc_mergeinfo && mergeinfo)
2156 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2157 iterpool));
2158 else if (! mergeinfo)
2159 mergeinfo = wc_mergeinfo;
2160
2161 if (mergeinfo)
2162 {
2163 /* Push a mergeinfo prop representing MERGEINFO onto the
2164 * OUTGOING_PROP_CHANGES array. */
2165
2166 svn_prop_t *mergeinfo_prop
2167 = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2168 svn_string_t *prop_value;
2169
2170 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2171 scratch_pool));
2172
2173 if (!item->outgoing_prop_changes)
2174 {
2175 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2176 sizeof(svn_prop_t *));
2177 }
2178
2179 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2180 mergeinfo_prop->value = prop_value;
2181 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2182 = mergeinfo_prop;
2183 }
2184
2185 if (pin_externals)
2186 {
2187 apr_hash_t *pinned_externals;
2188 apr_hash_index_t *hi;
2189
2190 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2191 externals_to_pin, pair,
2192 ra_session, cukb.repos_root_url,
2193 ctx, scratch_pool, iterpool));
2194 for (hi = apr_hash_first(scratch_pool, pinned_externals);
2195 hi;
2196 hi = apr_hash_next(hi))
2197 {
2198 const char *dst_relpath = apr_hash_this_key(hi);
2199 svn_string_t *externals_propval = apr_hash_this_val(hi);
2200 const char *dst_url;
2201 const char *commit_url;
2202 const char *src_abspath;
2203
2204 if (svn_path_is_url(pair->dst_abspath_or_url))
2205 dst_url = pair->dst_abspath_or_url;
2206 else
2207 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2208 pair->dst_abspath_or_url,
2209 scratch_pool, iterpool));
2210 commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2211 scratch_pool);
2212 src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2213 dst_relpath, iterpool);
2214 SVN_ERR(queue_prop_change_commit_items(src_abspath,
2215 commit_url, commit_items,
2216 SVN_PROP_EXTERNALS,
2217 externals_propval,
2218 scratch_pool, iterpool));
2219 }
2220 }
2221 }
2222
2223 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2224 {
2225 const char *tmp_file;
2226
2227 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2228 ctx, scratch_pool));
2229 if (! message)
2230 {
2231 svn_pool_destroy(iterpool);
2232 svn_pool_destroy(session_pool);
2233 return SVN_NO_ERROR;
2234 }
2235 }
2236 else
2237 message = "";
2238
2239 /* Sort and condense our COMMIT_ITEMS. */
2240 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2241 commit_items, scratch_pool));
2242
2243 /* Add the commit items to the DAV commit item list to provide access
2244 to dav properties (for pre http-v2 DAV) */
2245 apr_array_cat(commit_items_for_dav, commit_items);
2246
2247 #ifdef ENABLE_EV2_SHIMS
2248 if (commit_items)
2249 {
2250 relpath_map = apr_hash_make(scratch_pool);
2251 for (i = 0; i < commit_items->nelts; i++)
2252 {
2253 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2254 svn_client_commit_item3_t *);
2255 const char *relpath;
2256
2257 if (!item->path)
2258 continue;
2259
2260 svn_pool_clear(iterpool);
2261 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2262 NULL, NULL,
2263 ctx->wc_ctx, item->path, FALSE,
2264 scratch_pool, iterpool));
2265 if (relpath)
2266 svn_hash_sets(relpath_map, relpath, item->path);
2267 }
2268 }
2269 #endif
2270
2271 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2272
2273 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2274 message, ctx, session_pool));
2275
2276 /* Fetch RA commit editor. */
2277 #ifdef ENABLE_EV2_SHIMS
2278 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2279 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2280 session_pool)));
2281 #endif
2282 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2283 commit_revprops,
2284 commit_callback,
2285 commit_baton, NULL,
2286 TRUE, /* No lock tokens */
2287 session_pool));
2288
2289 /* Perform the commit. */
2290 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2291 editor, edit_baton,
2292 NULL /* notify_path_prefix */,
2293 NULL, ctx, session_pool, session_pool),
2294 _("Commit failed (details follow):"));
2295
2296 svn_pool_destroy(iterpool);
2297 svn_pool_destroy(session_pool);
2298
2299 return SVN_NO_ERROR;
2300 }
2301
2302 /* A baton for notification_adjust_func(). */
2303 struct notification_adjust_baton
2304 {
2305 svn_wc_notify_func2_t inner_func;
2306 void *inner_baton;
2307 const char *checkout_abspath;
2308 const char *final_abspath;
2309 };
2310
2311 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2312 * baton is BATON->inner_baton) to turn the result of a 'checkout' into
2313 * what we want to see for a 'copy to WC' operation.
2314 *
2315 * - Adjust the notification paths that start with BATON->checkout_abspath
2316 * to start instead with BATON->final_abspath.
2317 * - Change start-of-update notification into a plain WC 'add' for the root.
2318 * - Change checkout 'add' notifications into a plain WC 'add'.
2319 * - Discard 'update_completed' notifications.
2320 */
2321 static void
notification_adjust_func(void * baton,const svn_wc_notify_t * notify,apr_pool_t * pool)2322 notification_adjust_func(void *baton,
2323 const svn_wc_notify_t *notify,
2324 apr_pool_t *pool)
2325 {
2326 struct notification_adjust_baton *nb = baton;
2327 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2328 const char *relpath;
2329
2330 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2331 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2332
2333 /* Convert 'update' notifications to plain 'add' notifications; discard
2334 notifications about checkout/update starting/finishing. */
2335 if (notify->action == svn_wc_notify_update_started /* root */
2336 || notify->action == svn_wc_notify_update_add) /* non-root */
2337 {
2338 inner_notify->action = svn_wc_notify_add;
2339 }
2340 else if (notify->action == svn_wc_notify_update_update
2341 || notify->action == svn_wc_notify_update_completed)
2342 {
2343 /* update_update happens only for a prop mod on root; the root was
2344 already notified so discard this */
2345 return;
2346 }
2347
2348 if (nb->inner_func)
2349 nb->inner_func(nb->inner_baton, inner_notify, pool);
2350 }
2351
2352 /** Copy a directory tree from a remote repository.
2353 *
2354 * Copy from RA_SESSION:LOCATION to WC_CTX:DST_ABSPATH.
2355 *
2356 * Create the directory DST_ABSPATH, if not present. Its parent should be
2357 * already under version control in the WC and in a suitable state for
2358 * scheduling the addition of a child.
2359 *
2360 * Ignore any incoming non-regular properties (entry-props, DAV/WC-props).
2361 * Remove any incoming 'svn:mergeinfo' properties.
2362 */
2363 static svn_error_t *
copy_foreign_dir(svn_ra_session_t * ra_session,const svn_client__pathrev_t * location,const char * dst_abspath,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2364 copy_foreign_dir(svn_ra_session_t *ra_session,
2365 const svn_client__pathrev_t *location,
2366 const char *dst_abspath,
2367 svn_wc_notify_func2_t notify_func,
2368 void *notify_baton,
2369 svn_cancel_func_t cancel_func,
2370 void *cancel_baton,
2371 svn_client_ctx_t *ctx,
2372 apr_pool_t *scratch_pool)
2373 {
2374 const svn_delta_editor_t *editor;
2375 void *eb;
2376 const svn_delta_editor_t *wrapped_editor;
2377 void *wrapped_baton;
2378 const svn_ra_reporter3_t *reporter;
2379 void *reporter_baton;
2380
2381 /* Get a WC editor. It does not need an RA session because we will not
2382 be sending it any 'copy from' requests, only 'add' requests. */
2383 SVN_ERR(svn_client__wc_editor_internal(&editor, &eb,
2384 dst_abspath,
2385 TRUE /*root_dir_add*/,
2386 TRUE /*ignore_mergeinfo_changes*/,
2387 FALSE /*manage_wc_write_lock*/,
2388 notify_func, notify_baton,
2389 NULL /*ra_session*/,
2390 ctx, scratch_pool));
2391
2392 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2393 editor, eb,
2394 &wrapped_editor, &wrapped_baton,
2395 scratch_pool));
2396
2397 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
2398 location->rev, "", svn_depth_infinity,
2399 FALSE, FALSE, wrapped_editor, wrapped_baton,
2400 scratch_pool, scratch_pool));
2401
2402 SVN_ERR(reporter->set_path(reporter_baton, "", location->rev,
2403 svn_depth_infinity /* irrelevant */,
2404 TRUE /*start_empty*/,
2405 NULL, scratch_pool));
2406
2407 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
2408
2409 return SVN_NO_ERROR;
2410 }
2411
2412 /* Implementation of svn_client__repos_to_wc_copy() for a dir.
2413 */
2414 static svn_error_t *
svn_client__repos_to_wc_copy_dir(svn_boolean_t * timestamp_sleep,const char * src_url,svn_revnum_t src_revnum,const char * dst_abspath,svn_boolean_t same_repositories,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2415 svn_client__repos_to_wc_copy_dir(svn_boolean_t *timestamp_sleep,
2416 const char *src_url,
2417 svn_revnum_t src_revnum,
2418 const char *dst_abspath,
2419 svn_boolean_t same_repositories,
2420 svn_ra_session_t *ra_session,
2421 svn_client_ctx_t *ctx,
2422 apr_pool_t *scratch_pool)
2423 {
2424 const char *tmpdir_abspath, *tmp_abspath;
2425
2426 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2427
2428 if (!same_repositories)
2429 {
2430 svn_client__pathrev_t *location;
2431
2432 *timestamp_sleep = TRUE;
2433
2434 /* ### Reparenting "ra_session" can't be right, can it? As this is
2435 a foreign repo, surely we need a new RA session? */
2436 SVN_ERR(svn_client__pathrev_create_with_session(&location, ra_session,
2437 src_revnum, src_url,
2438 scratch_pool));
2439 SVN_ERR(svn_ra_reparent(ra_session, src_url, scratch_pool));
2440 SVN_ERR(copy_foreign_dir(ra_session, location,
2441 dst_abspath,
2442 ctx->notify_func2, ctx->notify_baton2,
2443 ctx->cancel_func, ctx->cancel_baton,
2444 ctx, scratch_pool));
2445
2446 return SVN_NO_ERROR;
2447 }
2448
2449 /* Find a temporary location in which to check out the copy source. */
2450 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2451 scratch_pool, scratch_pool));
2452
2453 /* Get a temporary path. The crude way we do this is to create a
2454 temporary file, remember its name, and let it be deleted immediately. */
2455 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2456 svn_io_file_del_on_close,
2457 scratch_pool, scratch_pool));
2458
2459 /* Make a new checkout of the requested source. While doing so,
2460 * resolve copy_src_revnum to an actual revision number in case it
2461 * was until now 'invalid' meaning 'head'. Ask this function not to
2462 * sleep for timestamps, by passing a sleep_needed output param.
2463 * Send notifications for all nodes except the root node, and adjust
2464 * them to refer to the destination rather than this temporary path. */
2465 {
2466 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2467 void *old_notify_baton2 = ctx->notify_baton2;
2468 struct notification_adjust_baton nb;
2469 svn_error_t *err;
2470 svn_opt_revision_t copy_src_revision;
2471
2472 copy_src_revision.kind = svn_opt_revision_number;
2473 copy_src_revision.value.number = src_revnum;
2474
2475 nb.inner_func = ctx->notify_func2;
2476 nb.inner_baton = ctx->notify_baton2;
2477 nb.checkout_abspath = tmp_abspath;
2478 nb.final_abspath = dst_abspath;
2479 ctx->notify_func2 = notification_adjust_func;
2480 ctx->notify_baton2 = &nb;
2481
2482 err = svn_client__checkout_internal(NULL /*result_rev*/, timestamp_sleep,
2483 src_url,
2484 tmp_abspath,
2485 ©_src_revision,
2486 ©_src_revision,
2487 svn_depth_infinity,
2488 TRUE /*ignore_externals*/,
2489 FALSE, /* we don't allow obstructions */
2490 ra_session, ctx, scratch_pool);
2491
2492 ctx->notify_func2 = old_notify_func2;
2493 ctx->notify_baton2 = old_notify_baton2;
2494
2495 SVN_ERR(err);
2496 }
2497
2498 /* Schedule dst_path for addition in parent, with copy history.
2499 Don't send any notification here.
2500 Then remove the temporary checkout's .svn dir in preparation for
2501 moving the rest of it into the final destination. */
2502 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2503 TRUE /* metadata_only */,
2504 NULL, NULL, /* don't allow user to cancel here */
2505 NULL, NULL, scratch_pool));
2506 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2507 FALSE, scratch_pool, scratch_pool));
2508 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2509 tmp_abspath,
2510 FALSE, FALSE,
2511 NULL, NULL, /* don't cancel */
2512 scratch_pool));
2513
2514 /* Move the temporary disk tree into place. */
2515 SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, scratch_pool));
2516
2517 return SVN_NO_ERROR;
2518 }
2519
2520 /* Implementation of svn_client__repos_to_wc_copy() for a file.
2521 *
2522 * This has no 'ignore_externals' parameter because we don't support the
2523 * 'svn:externals' property being set on a file.
2524 */
2525 static svn_error_t *
svn_client__repos_to_wc_copy_file(svn_boolean_t * timestamp_sleep,const char * src_url,svn_revnum_t src_rev,const char * dst_abspath,svn_boolean_t same_repositories,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2526 svn_client__repos_to_wc_copy_file(svn_boolean_t *timestamp_sleep,
2527 const char *src_url,
2528 svn_revnum_t src_rev,
2529 const char *dst_abspath,
2530 svn_boolean_t same_repositories,
2531 svn_ra_session_t *ra_session,
2532 svn_client_ctx_t *ctx,
2533 apr_pool_t *scratch_pool)
2534 {
2535 const char *src_rel;
2536 apr_hash_t *new_props;
2537 svn_stream_t *new_base_contents = svn_stream_buffered(scratch_pool);
2538
2539 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, src_url,
2540 scratch_pool));
2541 /* Fetch the file content. */
2542 SVN_ERR(svn_ra_get_file(ra_session, src_rel, src_rev,
2543 new_base_contents, NULL, &new_props,
2544 scratch_pool));
2545 if (!same_repositories)
2546 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2547
2548 *timestamp_sleep = TRUE;
2549 SVN_ERR(svn_wc_add_repos_file4(
2550 ctx->wc_ctx, dst_abspath,
2551 new_base_contents, NULL, new_props, NULL,
2552 same_repositories ? src_url : NULL,
2553 same_repositories ? src_rev : SVN_INVALID_REVNUM,
2554 ctx->cancel_func, ctx->cancel_baton,
2555 scratch_pool));
2556 /* Do our own notification for the root node, even if we could possibly
2557 have delegated it. See also issue #2198. */
2558 if (ctx->notify_func2)
2559 {
2560 svn_wc_notify_t *notify
2561 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool);
2562
2563 notify->kind = svn_node_file;
2564 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2565 }
2566 return SVN_NO_ERROR;
2567 }
2568
2569 /* Are RA_SESSION and the versioned *parent* dir of WC_TARGET_ABSPATH in
2570 * the same repository?
2571 */
2572 static svn_error_t *
is_same_repository(svn_boolean_t * same_repository,svn_ra_session_t * ra_session,const char * wc_target_abspath,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2573 is_same_repository(svn_boolean_t *same_repository,
2574 svn_ra_session_t *ra_session,
2575 const char *wc_target_abspath,
2576 svn_client_ctx_t *ctx,
2577 apr_pool_t *scratch_pool)
2578 {
2579 const char *src_uuid, *dst_uuid;
2580
2581 /* Get the repository UUIDs of copy source URL and WC parent path */
2582 SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, scratch_pool));
2583 SVN_ERR(svn_client_get_repos_root(NULL /*root_url*/, &dst_uuid,
2584 svn_dirent_dirname(wc_target_abspath,
2585 scratch_pool),
2586 ctx, scratch_pool, scratch_pool));
2587 *same_repository = (strcmp(src_uuid, dst_uuid) == 0);
2588 return SVN_NO_ERROR;
2589 }
2590
2591 svn_error_t *
svn_client__repos_to_wc_copy_internal(svn_boolean_t * timestamp_sleep,svn_node_kind_t kind,const char * src_url,svn_revnum_t src_rev,const char * dst_abspath,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2592 svn_client__repos_to_wc_copy_internal(svn_boolean_t *timestamp_sleep,
2593 svn_node_kind_t kind,
2594 const char *src_url,
2595 svn_revnum_t src_rev,
2596 const char *dst_abspath,
2597 svn_ra_session_t *ra_session,
2598 svn_client_ctx_t *ctx,
2599 apr_pool_t *scratch_pool)
2600 {
2601 const char *old_session_url;
2602 svn_boolean_t timestamp_sleep_ignored;
2603 svn_boolean_t same_repositories;
2604
2605 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
2606 src_url, scratch_pool));
2607
2608 SVN_ERR(is_same_repository(&same_repositories,
2609 ra_session, dst_abspath, ctx, scratch_pool));
2610
2611 if (!timestamp_sleep)
2612 timestamp_sleep = ×tamp_sleep_ignored;
2613
2614 if (kind == svn_node_dir)
2615 {
2616 SVN_ERR(svn_client__repos_to_wc_copy_dir(timestamp_sleep,
2617 src_url, src_rev,
2618 dst_abspath,
2619 same_repositories,
2620 ra_session,
2621 ctx, scratch_pool));
2622 }
2623 else if (kind == svn_node_file)
2624 {
2625 SVN_ERR(svn_client__repos_to_wc_copy_file(timestamp_sleep,
2626 src_url, src_rev,
2627 dst_abspath,
2628 same_repositories,
2629 ra_session,
2630 ctx, scratch_pool));
2631 }
2632
2633 /* Reparent the session back to the original URL. */
2634 SVN_ERR(svn_ra_reparent(ra_session, old_session_url, scratch_pool));
2635 return SVN_NO_ERROR;
2636 }
2637
2638 svn_error_t *
svn_client__repos_to_wc_copy_by_editor(svn_boolean_t * timestamp_sleep,svn_node_kind_t kind,const char * src_url,svn_revnum_t src_rev,const char * dst_abspath,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2639 svn_client__repos_to_wc_copy_by_editor(svn_boolean_t *timestamp_sleep,
2640 svn_node_kind_t kind,
2641 const char *src_url,
2642 svn_revnum_t src_rev,
2643 const char *dst_abspath,
2644 svn_ra_session_t *ra_session,
2645 svn_client_ctx_t *ctx,
2646 apr_pool_t *scratch_pool)
2647 {
2648 const svn_delta_editor_t *editor;
2649 void *eb;
2650 const char *src_anchor = svn_uri_dirname(src_url, scratch_pool);
2651 const char *dst_target = svn_dirent_basename(dst_abspath, scratch_pool);
2652 void *rb, *db;
2653
2654 SVN_ERR(svn_ra_reparent(ra_session, src_anchor, scratch_pool));
2655
2656 SVN_ERR(svn_client__wc_editor_internal(
2657 &editor, &eb,
2658 svn_dirent_dirname(dst_abspath, scratch_pool),
2659 FALSE /*root_dir_add*/,
2660 FALSE /*ignore_mergeinfo_changes*/,
2661 FALSE /*manage_wc_write_lock*/,
2662 ctx->notify_func2, ctx->notify_baton2,
2663 ra_session,
2664 ctx, scratch_pool));
2665
2666 SVN_ERR(editor->open_root(eb, SVN_INVALID_REVNUM, scratch_pool, &rb));
2667 if (kind == svn_node_dir)
2668 {
2669 SVN_ERR(editor->add_directory(dst_target, rb,
2670 src_url, src_rev,
2671 scratch_pool,
2672 &db));
2673 SVN_ERR(editor->close_directory(db, scratch_pool));
2674 }
2675 else
2676 {
2677 SVN_ERR(editor->add_file(dst_target, rb,
2678 src_url, src_rev,
2679 scratch_pool,
2680 &db));
2681 SVN_ERR(editor->close_file(db, NULL, scratch_pool));
2682 }
2683 SVN_ERR(editor->close_edit(eb, scratch_pool));
2684
2685 if (timestamp_sleep)
2686 *timestamp_sleep = TRUE;
2687 return SVN_NO_ERROR;
2688 }
2689
2690 /* Peform each individual copy operation for a repos -> wc copy. A
2691 helper for repos_to_wc_copy().
2692
2693 PAIR->src_revnum PAIR->src_abspath_or_url should already have been
2694 resolved to the operative revision number and operative URL.
2695 */
2696 static svn_error_t *
repos_to_wc_copy_single(svn_boolean_t * timestamp_sleep,const svn_client__copy_pair_t * pair,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * pool)2697 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2698 const svn_client__copy_pair_t *pair,
2699 svn_boolean_t ignore_externals,
2700 svn_boolean_t pin_externals,
2701 const apr_hash_t *externals_to_pin,
2702 svn_ra_session_t *ra_session,
2703 svn_client_ctx_t *ctx,
2704 apr_pool_t *pool)
2705 {
2706 apr_hash_t *src_mergeinfo;
2707 const char *dst_abspath = pair->dst_abspath_or_url;
2708 svn_boolean_t same_repositories;
2709
2710 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(pair->src_revnum));
2711 SVN_ERR_ASSERT(svn_path_is_url(pair->src_abspath_or_url));
2712 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2713
2714 SVN_ERR(is_same_repository(&same_repositories,
2715 ra_session, dst_abspath, ctx, pool));
2716 if (!same_repositories && ctx->notify_func2)
2717 {
2718 svn_wc_notify_t *notify;
2719 notify = svn_wc_create_notify_url(
2720 pair->src_abspath_or_url,
2721 svn_wc_notify_foreign_copy_begin,
2722 pool);
2723 notify->kind = pair->src_kind;
2724 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2725
2726 /* Allow a theoretical cancel to get through. */
2727 if (ctx->cancel_func)
2728 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2729 }
2730
2731 SVN_ERR(svn_client__repos_to_wc_copy_by_editor(
2732 timestamp_sleep,
2733 pair->src_kind,
2734 pair->src_abspath_or_url,
2735 pair->src_revnum,
2736 dst_abspath,
2737 ra_session, ctx, pool));
2738
2739 /* Fetch externals, pinning them if requested */
2740 if (!ignore_externals && pair->src_kind == svn_node_dir)
2741 {
2742 if (same_repositories)
2743 {
2744 const char *repos_root_url;
2745 apr_hash_t *new_externals;
2746 apr_hash_t *new_depths;
2747
2748 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2749
2750 if (pin_externals)
2751 {
2752 apr_hash_t *pinned_externals;
2753 apr_hash_index_t *hi;
2754 apr_pool_t *iterpool;
2755
2756 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2757 externals_to_pin, pair,
2758 ra_session, repos_root_url,
2759 ctx, pool, pool));
2760
2761 iterpool = svn_pool_create(pool);
2762 for (hi = apr_hash_first(pool, pinned_externals);
2763 hi;
2764 hi = apr_hash_next(hi))
2765 {
2766 const char *dst_relpath = apr_hash_this_key(hi);
2767 svn_string_t *externals_propval = apr_hash_this_val(hi);
2768 const char *local_abspath;
2769
2770 svn_pool_clear(iterpool);
2771
2772 local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2773 dst_relpath, iterpool);
2774 /* ### use a work queue? */
2775 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2776 SVN_PROP_EXTERNALS, externals_propval,
2777 svn_depth_empty, TRUE /* skip_checks */,
2778 NULL /* changelist_filter */,
2779 ctx->cancel_func, ctx->cancel_baton,
2780 NULL, NULL, /* no extra notification */
2781 iterpool));
2782 }
2783 svn_pool_destroy(iterpool);
2784 }
2785
2786 /* Now update all externals in the newly created copy. */
2787 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2788 &new_depths,
2789 ctx->wc_ctx,
2790 dst_abspath,
2791 svn_depth_infinity,
2792 pool, pool));
2793 SVN_ERR(svn_client__handle_externals(new_externals,
2794 new_depths,
2795 repos_root_url, dst_abspath,
2796 svn_depth_infinity,
2797 timestamp_sleep,
2798 ra_session,
2799 ctx, pool));
2800 }
2801 }
2802
2803 if (same_repositories)
2804 {
2805 /* Record the implied mergeinfo. */
2806 SVN_ERR(svn_client__get_repos_mergeinfo(&src_mergeinfo, ra_session,
2807 pair->src_abspath_or_url,
2808 pair->src_revnum,
2809 svn_mergeinfo_inherited,
2810 TRUE /*squelch_incapable*/,
2811 pool));
2812 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2813
2814 /* ### Maybe the notification should mention this mergeinfo change. */
2815 /* ### Maybe we should do this during rather than after the copy. */
2816 }
2817
2818 return SVN_NO_ERROR;
2819 }
2820
2821 static svn_error_t *
repos_to_wc_copy_locked(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,const char * top_dst_abspath,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2822 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2823 const apr_array_header_t *copy_pairs,
2824 const char *top_dst_abspath,
2825 svn_boolean_t ignore_externals,
2826 svn_boolean_t pin_externals,
2827 const apr_hash_t *externals_to_pin,
2828 svn_ra_session_t *ra_session,
2829 svn_client_ctx_t *ctx,
2830 apr_pool_t *scratch_pool)
2831 {
2832 int i;
2833 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2834
2835 /* Perform the move for each of the copy_pairs. */
2836 for (i = 0; i < copy_pairs->nelts; i++)
2837 {
2838 /* Check for cancellation */
2839 if (ctx->cancel_func)
2840 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2841
2842 svn_pool_clear(iterpool);
2843
2844 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2845 APR_ARRAY_IDX(copy_pairs, i,
2846 svn_client__copy_pair_t *),
2847 ignore_externals,
2848 pin_externals, externals_to_pin,
2849 ra_session, ctx, iterpool));
2850 }
2851 svn_pool_destroy(iterpool);
2852
2853 return SVN_NO_ERROR;
2854 }
2855
2856 static svn_error_t *
repos_to_wc_copy(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * pool)2857 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2858 const apr_array_header_t *copy_pairs,
2859 svn_boolean_t ignore_externals,
2860 svn_boolean_t pin_externals,
2861 const apr_hash_t *externals_to_pin,
2862 svn_client_ctx_t *ctx,
2863 apr_pool_t *pool)
2864 {
2865 svn_ra_session_t *ra_session;
2866 const char *top_src_url, *top_dst_abspath;
2867 apr_pool_t *iterpool = svn_pool_create(pool);
2868 const char *lock_abspath;
2869 int i;
2870
2871 /* Get the real path for the source, based upon its peg revision. */
2872 for (i = 0; i < copy_pairs->nelts; i++)
2873 {
2874 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2875 svn_client__copy_pair_t *);
2876 const char *src;
2877
2878 svn_pool_clear(iterpool);
2879
2880 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2881 NULL,
2882 pair->src_abspath_or_url,
2883 &pair->src_peg_revision,
2884 &pair->src_op_revision, NULL,
2885 ctx, iterpool));
2886
2887 pair->src_original = pair->src_abspath_or_url;
2888 pair->src_abspath_or_url = apr_pstrdup(pool, src);
2889 }
2890
2891 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_abspath,
2892 NULL, pool));
2893 lock_abspath = top_dst_abspath;
2894 if (copy_pairs->nelts == 1)
2895 {
2896 top_src_url = svn_uri_dirname(top_src_url, pool);
2897 lock_abspath = svn_dirent_dirname(top_dst_abspath, pool);
2898 }
2899
2900 /* Open a repository session to the longest common src ancestor. We do not
2901 (yet) have a working copy, so we don't have a corresponding path and
2902 tempfiles cannot go into the admin area. */
2903 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2904 ctx, pool, pool));
2905
2906 /* Get the correct src path for the peg revision used, and verify that we
2907 aren't overwriting an existing path. */
2908 for (i = 0; i < copy_pairs->nelts; i++)
2909 {
2910 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2911 svn_client__copy_pair_t *);
2912 const char *src_rel;
2913
2914 svn_pool_clear(iterpool);
2915
2916 /* Next, make sure that the path exists in the repository. */
2917 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2918 pair->src_abspath_or_url,
2919 iterpool));
2920 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2921 &pair->src_kind, pool));
2922 if (pair->src_kind == svn_node_none)
2923 {
2924 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2925 return svn_error_createf
2926 (SVN_ERR_FS_NOT_FOUND, NULL,
2927 _("Path '%s' not found in revision %ld"),
2928 pair->src_abspath_or_url, pair->src_revnum);
2929 else
2930 return svn_error_createf
2931 (SVN_ERR_FS_NOT_FOUND, NULL,
2932 _("Path '%s' not found in head revision"),
2933 pair->src_abspath_or_url);
2934 }
2935 }
2936 svn_pool_destroy(iterpool);
2937
2938 SVN_WC__CALL_WITH_WRITE_LOCK(
2939 repos_to_wc_copy_locked(timestamp_sleep,
2940 copy_pairs, top_dst_abspath, ignore_externals,
2941 pin_externals, externals_to_pin,
2942 ra_session, ctx, pool),
2943 ctx->wc_ctx, lock_abspath, FALSE, pool);
2944 return SVN_NO_ERROR;
2945 }
2946
2947 #define NEED_REPOS_REVNUM(revision) \
2948 ((revision.kind != svn_opt_revision_unspecified) \
2949 && (revision.kind != svn_opt_revision_working))
2950
2951 /* ...
2952 *
2953 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2954 * change *TIMESTAMP_SLEEP. This output will be valid even if the
2955 * function returns an error.
2956 *
2957 * Perform all allocations in POOL.
2958 */
2959 static svn_error_t *
try_copy(svn_boolean_t * timestamp_sleep,const apr_array_header_t * sources,const char * dst_path_in,svn_boolean_t is_move,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_boolean_t make_parents,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)2960 try_copy(svn_boolean_t *timestamp_sleep,
2961 const apr_array_header_t *sources,
2962 const char *dst_path_in,
2963 svn_boolean_t is_move,
2964 svn_boolean_t allow_mixed_revisions,
2965 svn_boolean_t metadata_only,
2966 svn_boolean_t make_parents,
2967 svn_boolean_t ignore_externals,
2968 svn_boolean_t pin_externals,
2969 const apr_hash_t *externals_to_pin,
2970 const apr_hash_t *revprop_table,
2971 svn_commit_callback2_t commit_callback,
2972 void *commit_baton,
2973 svn_client_ctx_t *ctx,
2974 apr_pool_t *pool)
2975 {
2976 apr_array_header_t *copy_pairs =
2977 apr_array_make(pool, sources->nelts,
2978 sizeof(svn_client__copy_pair_t *));
2979 svn_boolean_t srcs_are_urls, dst_is_url;
2980 int i;
2981
2982 /* Assert instead of crashing if the sources list is empty. */
2983 SVN_ERR_ASSERT(sources->nelts > 0);
2984
2985 /* Are either of our paths URLs? Just check the first src_path. If
2986 there are more than one, we'll check for homogeneity among them
2987 down below. */
2988 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2989 svn_client_copy_source_t *)->path);
2990 dst_is_url = svn_path_is_url(dst_path_in);
2991 if (!dst_is_url)
2992 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2993
2994 /* If we have multiple source paths, it implies the dst_path is a
2995 directory we are moving or copying into. Populate the COPY_PAIRS
2996 array to contain a destination path for each of the source paths. */
2997 if (sources->nelts > 1)
2998 {
2999 apr_pool_t *iterpool = svn_pool_create(pool);
3000
3001 for (i = 0; i < sources->nelts; i++)
3002 {
3003 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
3004 svn_client_copy_source_t *);
3005 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
3006 const char *src_basename;
3007 svn_boolean_t src_is_url = svn_path_is_url(source->path);
3008
3009 svn_pool_clear(iterpool);
3010
3011 if (src_is_url)
3012 {
3013 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
3014 src_basename = svn_uri_basename(pair->src_abspath_or_url,
3015 iterpool);
3016 }
3017 else
3018 {
3019 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
3020 source->path, pool));
3021 src_basename = svn_dirent_basename(pair->src_abspath_or_url,
3022 iterpool);
3023 }
3024
3025 pair->src_op_revision = *source->revision;
3026 pair->src_peg_revision = *source->peg_revision;
3027 pair->src_kind = svn_node_unknown;
3028
3029 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
3030 &pair->src_op_revision,
3031 src_is_url,
3032 TRUE,
3033 iterpool));
3034
3035 /* Check to see if all the sources are urls or all working copy
3036 * paths. */
3037 if (src_is_url != srcs_are_urls)
3038 return svn_error_create
3039 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3040 _("Cannot mix repository and working copy sources"));
3041
3042 if (dst_is_url)
3043 pair->dst_abspath_or_url =
3044 svn_path_url_add_component2(dst_path_in, src_basename, pool);
3045 else
3046 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
3047 src_basename, pool);
3048 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
3049 }
3050
3051 svn_pool_destroy(iterpool);
3052 }
3053 else
3054 {
3055 /* Only one source path. */
3056 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
3057 svn_client_copy_source_t *source =
3058 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
3059 svn_boolean_t src_is_url = svn_path_is_url(source->path);
3060
3061 if (src_is_url)
3062 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
3063 else
3064 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
3065 source->path, pool));
3066 pair->src_op_revision = *source->revision;
3067 pair->src_peg_revision = *source->peg_revision;
3068 pair->src_kind = svn_node_unknown;
3069
3070 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
3071 &pair->src_op_revision,
3072 src_is_url, TRUE, pool));
3073
3074 pair->dst_abspath_or_url = dst_path_in;
3075 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
3076 }
3077
3078 if (!srcs_are_urls && !dst_is_url)
3079 {
3080 apr_pool_t *iterpool = svn_pool_create(pool);
3081
3082 for (i = 0; i < copy_pairs->nelts; i++)
3083 {
3084 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3085 svn_client__copy_pair_t *);
3086
3087 svn_pool_clear(iterpool);
3088
3089 if (svn_dirent_is_child(pair->src_abspath_or_url,
3090 pair->dst_abspath_or_url, iterpool))
3091 return svn_error_createf
3092 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3093 _("Cannot copy path '%s' into its own child '%s'"),
3094 svn_dirent_local_style(pair->src_abspath_or_url, pool),
3095 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
3096 }
3097
3098 svn_pool_destroy(iterpool);
3099 }
3100
3101 /* A file external should not be moved since the file external is
3102 implemented as a switched file and it would delete the file the
3103 file external is switched to, which is not the behavior the user
3104 would probably want. */
3105 if (is_move && !srcs_are_urls)
3106 {
3107 apr_pool_t *iterpool = svn_pool_create(pool);
3108
3109 for (i = 0; i < copy_pairs->nelts; i++)
3110 {
3111 svn_client__copy_pair_t *pair =
3112 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
3113 svn_node_kind_t external_kind;
3114 const char *defining_abspath;
3115
3116 svn_pool_clear(iterpool);
3117
3118 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3119 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
3120 NULL, NULL, NULL, ctx->wc_ctx,
3121 pair->src_abspath_or_url,
3122 pair->src_abspath_or_url, TRUE,
3123 iterpool, iterpool));
3124
3125 if (external_kind != svn_node_none)
3126 return svn_error_createf(
3127 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
3128 NULL,
3129 _("Cannot move the external at '%s'; please "
3130 "edit the svn:externals property on '%s'."),
3131 svn_dirent_local_style(pair->src_abspath_or_url, pool),
3132 svn_dirent_local_style(defining_abspath, pool));
3133 }
3134 svn_pool_destroy(iterpool);
3135 }
3136
3137 if (is_move)
3138 {
3139 /* Disallow moves between the working copy and the repository. */
3140 if (srcs_are_urls != dst_is_url)
3141 {
3142 return svn_error_create
3143 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3144 _("Moves between the working copy and the repository are not "
3145 "supported"));
3146 }
3147
3148 /* Disallow moving any path/URL onto or into itself. */
3149 for (i = 0; i < copy_pairs->nelts; i++)
3150 {
3151 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3152 svn_client__copy_pair_t *);
3153
3154 if (strcmp(pair->src_abspath_or_url,
3155 pair->dst_abspath_or_url) == 0)
3156 return svn_error_createf(
3157 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3158 srcs_are_urls ?
3159 _("Cannot move URL '%s' into itself") :
3160 _("Cannot move path '%s' into itself"),
3161 srcs_are_urls ?
3162 pair->src_abspath_or_url :
3163 svn_dirent_local_style(pair->src_abspath_or_url, pool));
3164 }
3165 }
3166 else
3167 {
3168 if (!srcs_are_urls)
3169 {
3170 /* If we are doing a wc->* copy, but with an operational revision
3171 other than the working copy revision, we are really doing a
3172 repo->* copy, because we're going to need to get the rev from the
3173 repo. */
3174
3175 svn_boolean_t need_repos_op_rev = FALSE;
3176 svn_boolean_t need_repos_peg_rev = FALSE;
3177
3178 /* Check to see if any revision is something other than
3179 svn_opt_revision_unspecified or svn_opt_revision_working. */
3180 for (i = 0; i < copy_pairs->nelts; i++)
3181 {
3182 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3183 svn_client__copy_pair_t *);
3184
3185 if (NEED_REPOS_REVNUM(pair->src_op_revision))
3186 need_repos_op_rev = TRUE;
3187
3188 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3189 need_repos_peg_rev = TRUE;
3190
3191 if (need_repos_op_rev || need_repos_peg_rev)
3192 break;
3193 }
3194
3195 if (need_repos_op_rev || need_repos_peg_rev)
3196 {
3197 apr_pool_t *iterpool = svn_pool_create(pool);
3198
3199 for (i = 0; i < copy_pairs->nelts; i++)
3200 {
3201 const char *copyfrom_repos_root_url;
3202 const char *copyfrom_repos_relpath;
3203 const char *url;
3204 svn_revnum_t copyfrom_rev;
3205 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3206 svn_client__copy_pair_t *);
3207
3208 svn_pool_clear(iterpool);
3209
3210 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3211
3212 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev,
3213 ©from_repos_relpath,
3214 ©from_repos_root_url,
3215 NULL, NULL, NULL,
3216 ctx->wc_ctx,
3217 pair->src_abspath_or_url,
3218 TRUE, iterpool, iterpool));
3219
3220 if (copyfrom_repos_relpath)
3221 url = svn_path_url_add_component2(copyfrom_repos_root_url,
3222 copyfrom_repos_relpath,
3223 pool);
3224 else
3225 return svn_error_createf
3226 (SVN_ERR_ENTRY_MISSING_URL, NULL,
3227 _("'%s' does not have a URL associated with it"),
3228 svn_dirent_local_style(pair->src_abspath_or_url, pool));
3229
3230 pair->src_abspath_or_url = url;
3231
3232 if (!need_repos_peg_rev
3233 || pair->src_peg_revision.kind == svn_opt_revision_base)
3234 {
3235 /* Default the peg revision to that of the WC entry. */
3236 pair->src_peg_revision.kind = svn_opt_revision_number;
3237 pair->src_peg_revision.value.number = copyfrom_rev;
3238 }
3239
3240 if (pair->src_op_revision.kind == svn_opt_revision_base)
3241 {
3242 /* Use the entry's revision as the operational rev. */
3243 pair->src_op_revision.kind = svn_opt_revision_number;
3244 pair->src_op_revision.value.number = copyfrom_rev;
3245 }
3246 }
3247
3248 svn_pool_destroy(iterpool);
3249 srcs_are_urls = TRUE;
3250 }
3251 }
3252 }
3253
3254 /* Now, call the right handler for the operation. */
3255 if ((! srcs_are_urls) && (! dst_is_url))
3256 {
3257 SVN_ERR(verify_wc_srcs(copy_pairs, ctx, pool));
3258 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only,
3259 ctx, pool, pool));
3260
3261 /* Copy or move all targets. */
3262 if (is_move)
3263 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3264 copy_pairs, dst_path_in,
3265 allow_mixed_revisions,
3266 metadata_only,
3267 ctx, pool));
3268 else
3269 {
3270 /* We ignore these values, so assert the default value */
3271 SVN_ERR_ASSERT(allow_mixed_revisions);
3272 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3273 copy_pairs,
3274 metadata_only,
3275 pin_externals,
3276 externals_to_pin,
3277 ctx, pool));
3278 }
3279 }
3280 else if ((! srcs_are_urls) && (dst_is_url))
3281 {
3282 return svn_error_trace(
3283 wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3284 commit_callback, commit_baton,
3285 pin_externals, externals_to_pin, ctx, pool));
3286 }
3287 else if ((srcs_are_urls) && (! dst_is_url))
3288 {
3289 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents,
3290 FALSE, FALSE /* metadata_only */,
3291 ctx, pool, pool));
3292
3293 return svn_error_trace(
3294 repos_to_wc_copy(timestamp_sleep,
3295 copy_pairs, ignore_externals,
3296 pin_externals, externals_to_pin, ctx, pool));
3297 }
3298 else
3299 {
3300 return svn_error_trace(
3301 repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3302 commit_callback, commit_baton, ctx, is_move,
3303 pin_externals, externals_to_pin, pool));
3304 }
3305 }
3306
3307
3308
3309 /* Public Interfaces */
3310 svn_error_t *
svn_client_copy7(const apr_array_header_t * sources,const char * dst_path,svn_boolean_t copy_as_child,svn_boolean_t make_parents,svn_boolean_t ignore_externals,svn_boolean_t metadata_only,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)3311 svn_client_copy7(const apr_array_header_t *sources,
3312 const char *dst_path,
3313 svn_boolean_t copy_as_child,
3314 svn_boolean_t make_parents,
3315 svn_boolean_t ignore_externals,
3316 svn_boolean_t metadata_only,
3317 svn_boolean_t pin_externals,
3318 const apr_hash_t *externals_to_pin,
3319 const apr_hash_t *revprop_table,
3320 svn_commit_callback2_t commit_callback,
3321 void *commit_baton,
3322 svn_client_ctx_t *ctx,
3323 apr_pool_t *pool)
3324 {
3325 svn_error_t *err;
3326 svn_boolean_t timestamp_sleep = FALSE;
3327 apr_pool_t *subpool = svn_pool_create(pool);
3328
3329 if (sources->nelts > 1 && !copy_as_child)
3330 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3331 NULL, NULL);
3332
3333 err = try_copy(×tamp_sleep,
3334 sources, dst_path,
3335 FALSE /* is_move */,
3336 TRUE /* allow_mixed_revisions */,
3337 metadata_only,
3338 make_parents,
3339 ignore_externals,
3340 pin_externals,
3341 externals_to_pin,
3342 revprop_table,
3343 commit_callback, commit_baton,
3344 ctx,
3345 subpool);
3346
3347 /* If the destination exists, try to copy the sources as children of the
3348 destination. */
3349 if (copy_as_child && err && (sources->nelts == 1)
3350 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3351 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3352 {
3353 const char *src_path = APR_ARRAY_IDX(sources, 0,
3354 svn_client_copy_source_t *)->path;
3355 const char *src_basename;
3356 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3357 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3358
3359 svn_error_clear(err);
3360 svn_pool_clear(subpool);
3361
3362 src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3363 : svn_dirent_basename(src_path, subpool);
3364 dst_path
3365 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3366 subpool)
3367 : svn_dirent_join(dst_path, src_basename, subpool);
3368
3369 err = try_copy(×tamp_sleep,
3370 sources, dst_path,
3371 FALSE /* is_move */,
3372 TRUE /* allow_mixed_revisions */,
3373 metadata_only,
3374 make_parents,
3375 ignore_externals,
3376 pin_externals,
3377 externals_to_pin,
3378 revprop_table,
3379 commit_callback, commit_baton,
3380 ctx,
3381 subpool);
3382 }
3383
3384 /* Sleep if required. DST_PATH is not a URL in these cases. */
3385 if (timestamp_sleep)
3386 svn_io_sleep_for_timestamps(dst_path, subpool);
3387
3388 svn_pool_destroy(subpool);
3389 return svn_error_trace(err);
3390 }
3391
3392
3393 svn_error_t *
svn_client_move7(const apr_array_header_t * src_paths,const char * dst_path,svn_boolean_t move_as_child,svn_boolean_t make_parents,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)3394 svn_client_move7(const apr_array_header_t *src_paths,
3395 const char *dst_path,
3396 svn_boolean_t move_as_child,
3397 svn_boolean_t make_parents,
3398 svn_boolean_t allow_mixed_revisions,
3399 svn_boolean_t metadata_only,
3400 const apr_hash_t *revprop_table,
3401 svn_commit_callback2_t commit_callback,
3402 void *commit_baton,
3403 svn_client_ctx_t *ctx,
3404 apr_pool_t *pool)
3405 {
3406 const svn_opt_revision_t head_revision
3407 = { svn_opt_revision_head, { 0 } };
3408 svn_error_t *err;
3409 svn_boolean_t timestamp_sleep = FALSE;
3410 int i;
3411 apr_pool_t *subpool = svn_pool_create(pool);
3412 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3413 sizeof(const svn_client_copy_source_t *));
3414
3415 if (src_paths->nelts > 1 && !move_as_child)
3416 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3417 NULL, NULL);
3418
3419 for (i = 0; i < src_paths->nelts; i++)
3420 {
3421 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3422 svn_client_copy_source_t *copy_source = apr_palloc(pool,
3423 sizeof(*copy_source));
3424
3425 copy_source->path = src_path;
3426 copy_source->revision = &head_revision;
3427 copy_source->peg_revision = &head_revision;
3428
3429 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3430 }
3431
3432 err = try_copy(×tamp_sleep,
3433 sources, dst_path,
3434 TRUE /* is_move */,
3435 allow_mixed_revisions,
3436 metadata_only,
3437 make_parents,
3438 FALSE /* ignore_externals */,
3439 FALSE /* pin_externals */,
3440 NULL /* externals_to_pin */,
3441 revprop_table,
3442 commit_callback, commit_baton,
3443 ctx,
3444 subpool);
3445
3446 /* If the destination exists, try to move the sources as children of the
3447 destination. */
3448 if (move_as_child && err && (src_paths->nelts == 1)
3449 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3450 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3451 {
3452 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3453 const char *src_basename;
3454 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3455 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3456
3457 svn_error_clear(err);
3458 svn_pool_clear(subpool);
3459
3460 src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3461 : svn_dirent_basename(src_path, pool);
3462 dst_path
3463 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3464 subpool)
3465 : svn_dirent_join(dst_path, src_basename, subpool);
3466
3467 err = try_copy(×tamp_sleep,
3468 sources, dst_path,
3469 TRUE /* is_move */,
3470 allow_mixed_revisions,
3471 metadata_only,
3472 make_parents,
3473 FALSE /* ignore_externals */,
3474 FALSE /* pin_externals */,
3475 NULL /* externals_to_pin */,
3476 revprop_table,
3477 commit_callback, commit_baton,
3478 ctx,
3479 subpool);
3480 }
3481
3482 /* Sleep if required. DST_PATH is not a URL in these cases. */
3483 if (timestamp_sleep)
3484 svn_io_sleep_for_timestamps(dst_path, subpool);
3485
3486 svn_pool_destroy(subpool);
3487 return svn_error_trace(err);
3488 }
3489