2 * copy.c: copy/move wrappers around wc 'copy' functionality.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
32 #include "svn_client.h"
33 #include "svn_error.h"
34 #include "svn_error_codes.h"
35 #include "svn_dirent_uri.h"
39 #include "svn_props.h"
40 #include "svn_mergeinfo.h"
41 #include "svn_pools.h"
44 #include "mergeinfo.h"
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"
54 * OUR BASIC APPROACH TO COPIES
55 * ============================
57 * for each source/destination pair
58 * if (not exist src_path)
59 * return ERR_BAD_SRC error
62 * return ERR_OBSTRUCTION error
64 * copy src_path into parent_of_dst_path as basename (dst_path)
74 /* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
75 MERGEINFO to any mergeinfo pre-existing in the WC. */
77 extend_wc_mergeinfo(const char *target_abspath,
78 apr_hash_t *mergeinfo,
79 svn_client_ctx_t *ctx,
82 apr_hash_t *wc_mergeinfo;
84 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
86 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
87 target_abspath, pool, pool));
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;
95 return svn_error_trace(
96 svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
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
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,
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;
119 svn_boolean_t src_is_url;
120 svn_boolean_t dst_is_url;
124 first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
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);
131 if (copy_pairs->nelts == 1)
132 top_dst = apr_pstrdup(subpool, first_dst);
134 top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
135 : svn_dirent_dirname(first_dst, subpool);
137 /* Sources can came from anywhere, so we have to actually do some
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++)
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,
153 const svn_client__copy_pair_t *pair =
154 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
157 ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
159 : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
164 *src_ancestor = apr_pstrdup(pool, top_src);
167 *dst_ancestor = apr_pstrdup(pool, top_dst);
172 ? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
173 : svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
175 svn_pool_destroy(subpool);
180 /* Quote a string if it would be handled as multiple or different tokens
181 during externals parsing */
183 maybe_quote(const char *value,
184 apr_pool_t *result_pool)
189 status = apr_tokenize_to_argv(value, &argv, result_pool);
191 if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0)
192 return apr_pstrdup(result_pool, value);
195 svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool);
198 svn_stringbuf_appendbyte(sb, '\"');
200 for (c = value; *c; c++)
202 if (*c == '\\' || *c == '\"' || *c == '\'')
203 svn_stringbuf_appendbyte(sb, '\\');
205 svn_stringbuf_appendbyte(sb, *c);
208 svn_stringbuf_appendbyte(sb, '\"');
211 status = apr_tokenize_to_argv(sb->data, &argv, result_pool);
213 SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1]
214 && !strcmp(argv[0], value));
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. */
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,
234 const char *peg_rev_str;
236 switch (info->format)
238 case svn_wc__external_description_format_1:
239 if (external_pegrev.kind == svn_opt_revision_unspecified)
241 /* If info->rev_str is NULL, this yields an empty string. */
242 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
244 else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
245 rev_str = apr_psprintf(pool, "%s ", info->rev_str);
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);
254 *new_external_description =
255 apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool),
257 maybe_quote(item->url, pool));
260 case svn_wc__external_description_format_2:
261 if (external_pegrev.kind == svn_opt_revision_unspecified)
263 /* If info->rev_str is NULL, this yields an empty string. */
264 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
266 else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
267 rev_str = apr_psprintf(pool, "%s ", info->rev_str);
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;
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);
284 *new_external_description =
285 apr_psprintf(pool, "%s%s %s\n", rev_str,
286 maybe_quote(apr_psprintf(pool, "%s%s", item->url,
289 maybe_quote(item->target_dir, pool));
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));
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.
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)
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;
326 apr_pool_t *iterpool;
328 SVN_ERR(svn_wc__parse_externals_description(&external_items,
330 local_abspath_or_url,
331 externals_prop_val->data,
332 FALSE /* canonicalize_url */,
335 if (externals_to_pin)
337 items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin,
338 local_abspath_or_url);
341 /* No pinning at all for this path. */
342 *pinned_externals = NULL;
349 buf = svn_stringbuf_create_empty(scratch_pool);
350 iterpool = svn_pool_create(scratch_pool);
352 for (i = 0; i < external_items->nelts; i++)
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;
359 svn_pool_clear(iterpool);
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 *);
367 svn_wc_external_item2_t *item_to_pin = NULL;
369 for (j = 0; j < items_to_pin->nelts; j++)
371 svn_wc_external_item2_t *const current =
372 APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *);
376 && 0 == strcmp(item->url, current->url)
377 && 0 == strcmp(item->target_dir, current->target_dir))
379 item_to_pin = current;
384 /* If this item is not in our list of external items to pin then
385 * simply keep the external at its original value. */
390 external_pegrev.kind = svn_opt_revision_unspecified;
391 SVN_ERR(make_external_description(&desc, local_abspath_or_url,
392 item, info, external_pegrev,
394 svn_stringbuf_appendcstr(buf, desc);
399 if (item->peg_revision.kind == svn_opt_revision_date)
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;
405 else if (item->peg_revision.kind == svn_opt_revision_number)
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;
414 item->peg_revision.kind == svn_opt_revision_head ||
415 item->peg_revision.kind == svn_opt_revision_unspecified);
417 /* We're actually going to change the peg revision. */
420 if (svn_path_is_url(local_abspath_or_url))
422 const char *resolved_url;
423 svn_ra_session_t *external_ra_session;
424 svn_revnum_t latest_revnum;
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,
435 SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
439 external_pegrev.kind = svn_opt_revision_number;
440 external_pegrev.value.number = latest_revnum;
444 const char *external_abspath;
445 svn_node_kind_t external_kind;
446 svn_revnum_t external_checked_out_rev;
448 external_abspath = svn_dirent_join(local_abspath_or_url,
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,
457 if (external_kind == svn_node_none)
458 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
460 _("Cannot pin external '%s' defined "
461 "in %s at '%s' because it is not "
462 "checked out in the working copy "
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)
471 svn_boolean_t is_switched;
472 svn_boolean_t is_modified;
473 svn_revnum_t min_rev;
474 svn_revnum_t max_rev;
476 /* Perform some sanity checks on the checked-out external. */
478 SVN_ERR(svn_wc__has_switched_subtrees(&is_switched,
480 external_abspath, NULL,
483 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
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),
496 SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
497 external_abspath, TRUE,
502 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
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),
516 SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx,
517 external_abspath, FALSE,
519 if (min_rev != max_rev)
520 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
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),
533 external_checked_out_rev = min_rev;
537 SVN_ERR_ASSERT(external_kind == svn_node_file);
538 SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev,
540 ctx->wc_ctx, external_abspath,
541 iterpool, iterpool));
544 external_pegrev.kind = svn_opt_revision_number;
545 external_pegrev.value.number = external_checked_out_rev;
549 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date ||
550 external_pegrev.kind == svn_opt_revision_number);
552 SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url,
553 item, info, external_pegrev, iterpool));
555 svn_stringbuf_appendcstr(buf, pinned_desc);
557 svn_pool_destroy(iterpool);
559 if (pinned_items > 0)
560 *pinned_externals = svn_string_create_from_buf(buf, result_pool);
562 *pinned_externals = NULL;
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. */
579 resolve_pinned_externals(apr_hash_t **pinned_externals,
580 const apr_hash_t *externals_to_pin,
581 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)
588 const char *old_url = NULL;
589 apr_hash_t *externals_props;
590 apr_hash_index_t *hi;
591 apr_pool_t *iterpool;
593 *pinned_externals = apr_hash_make(result_pool);
595 if (svn_path_is_url(pair->src_abspath_or_url))
597 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
598 pair->src_abspath_or_url,
600 externals_props = apr_hash_make(scratch_pool);
601 SVN_ERR(svn_client__remote_propget(externals_props, NULL,
603 pair->src_abspath_or_url, "",
613 SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
615 pair->src_abspath_or_url,
617 scratch_pool, scratch_pool));
619 /* ### gather_definitions returns propvals as const char * */
620 for (hi = apr_hash_first(scratch_pool, externals_props);
622 hi = apr_hash_next(hi))
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);
628 svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
632 if (apr_hash_count(externals_props) == 0)
635 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
639 iterpool = svn_pool_create(scratch_pool);
640 for (hi = apr_hash_first(scratch_pool, externals_props);
642 hi = apr_hash_next(hi))
644 const char *local_abspath_or_url = apr_hash_this_key(hi);
645 svn_string_t *externals_propval = apr_hash_this_val(hi);
647 svn_string_t *new_propval;
649 svn_pool_clear(iterpool);
651 SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
653 repos_root_url, local_abspath_or_url, ctx,
654 result_pool, iterpool));
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,
662 relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
663 local_abspath_or_url);
664 SVN_ERR_ASSERT(relpath);
666 svn_hash_sets(*pinned_externals, relpath, new_propval);
669 svn_pool_destroy(iterpool);
672 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
679 /* The guts of do_wc_to_wc_copies */
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)
691 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
692 svn_error_t *err = SVN_NO_ERROR;
694 for (i = 0; i < copy_pairs->nelts; i++)
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;
701 svn_pool_clear(iterpool);
703 /* Check for cancellation */
704 if (ctx->cancel_func)
705 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
709 const char *repos_root_url;
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,
718 iterpool, iterpool));
721 /* Perform the copy */
722 dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
724 *timestamp_sleep = TRUE;
725 err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
727 ctx->cancel_func, ctx->cancel_baton,
728 ctx->notify_func2, ctx->notify_baton2, iterpool);
732 if (pinned_externals)
734 apr_hash_index_t *hi;
736 for (hi = apr_hash_first(iterpool, pinned_externals);
738 hi = apr_hash_next(hi))
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;
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 */
757 svn_pool_destroy(iterpool);
763 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
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,
774 const char *dst_parent, *dst_parent_abspath;
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);
780 SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
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);
791 /* The locked bit of do_wc_to_wc_moves. */
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)
802 const char *dst_abspath;
804 dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
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,
817 /* Wrapper to add an optional second lock */
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)
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,
834 ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
836 SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
837 lock_dst, allow_mixed_revisions,
844 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
845 afterwards. Use POOL for temporary allocations. */
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,
856 apr_pool_t *iterpool = svn_pool_create(pool);
857 svn_error_t *err = SVN_NO_ERROR;
859 for (i = 0; i < copy_pairs->nelts; i++)
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;
866 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
867 svn_client__copy_pair_t *);
868 svn_pool_clear(iterpool);
870 /* Check for cancellation */
871 if (ctx->cancel_func)
872 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
874 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
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));
884 /* We now need to lock the right combination of batons.
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,
894 && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
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))
914 *timestamp_sleep = TRUE;
916 /* Perform the copy and then the delete. */
918 SVN_WC__CALL_WITH_WRITE_LOCK(
919 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
921 allow_mixed_revisions,
924 ctx->wc_ctx, src_parent_abspath,
927 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
929 allow_mixed_revisions,
934 svn_pool_destroy(iterpool);
936 return svn_error_trace(err);
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 */
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)
952 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
954 /* Check that DST does not exist, but its parent does */
955 for (i = 0; i < copy_pairs->nelts; i++)
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;
961 svn_pool_clear(iterpool);
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 */,
971 if (dst_kind != svn_node_none)
973 svn_boolean_t is_excluded;
974 svn_boolean_t is_server_excluded;
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,
981 if (is_excluded || is_server_excluded)
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));
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,
996 /* Check that there is no unversioned obstruction */
998 dst_kind = svn_node_none;
1000 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1003 if (dst_kind != svn_node_none)
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,
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. */
1018 SVN_ERR(svn_path_cstring_from_utf8(&dst,
1019 pair->dst_abspath_or_url,
1022 apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
1023 APR_FILEPATH_TRUENAME, iterpool);
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);
1031 /* else: Don't report this error; just report the normal error */
1033 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
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);
1039 svn_pool_destroy(iterpool);
1040 return SVN_NO_ERROR;
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,
1051 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1052 pair->dst_abspath_or_url, result_pool);
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,
1060 if (dst_parent_kind == svn_node_none)
1063 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1064 TRUE, ctx, iterpool));
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 "
1073 : _("Path '%s' is not a directory"),
1074 svn_dirent_local_style(
1075 pair->dst_parent_abspath,
1079 else if (dst_parent_kind != svn_node_dir)
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));
1087 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1088 &dst_parent_kind, scratch_pool));
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));
1097 svn_pool_destroy(iterpool);
1099 return SVN_NO_ERROR;
1102 static svn_error_t *
1103 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
1104 svn_boolean_t make_parents,
1105 svn_boolean_t is_move,
1106 svn_boolean_t metadata_only,
1107 svn_client_ctx_t *ctx,
1108 apr_pool_t *result_pool,
1109 apr_pool_t *scratch_pool)
1112 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1114 /* Check that all of our SRCs exist. */
1115 for (i = 0; i < copy_pairs->nelts; i++)
1117 svn_boolean_t deleted_ok;
1118 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1119 svn_client__copy_pair_t *);
1120 svn_pool_clear(iterpool);
1122 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1123 || pair->src_op_revision.kind == svn_opt_revision_base);
1125 /* Verify that SRC_PATH exists. */
1126 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1127 pair->src_abspath_or_url,
1128 deleted_ok, FALSE, iterpool));
1129 if (pair->src_kind == svn_node_none)
1130 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1131 _("Path '%s' does not exist"),
1132 svn_dirent_local_style(
1133 pair->src_abspath_or_url,
1137 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
1138 result_pool, iterpool));
1140 svn_pool_destroy(iterpool);
1142 return SVN_NO_ERROR;
1146 /* Path-specific state used as part of path_driver_cb_baton. */
1147 typedef struct path_driver_info_t
1149 const char *src_url;
1150 const char *src_path;
1151 const char *dst_path;
1152 svn_node_kind_t src_kind;
1153 svn_revnum_t src_revnum;
1154 svn_boolean_t resurrection;
1155 svn_boolean_t dir_add;
1156 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
1157 svn_string_t *externals; /* new externals definitions for the target */
1158 svn_boolean_t only_pin_externals;
1159 } path_driver_info_t;
1162 /* The baton used with the path_driver_cb_func() callback for a copy
1163 or move operation. */
1164 struct path_driver_cb_baton
1166 /* The editor (and its state) used to perform the operation. */
1167 const svn_delta_editor_t *editor;
1170 /* A hash of path -> path_driver_info_t *'s. */
1171 apr_hash_t *action_hash;
1173 /* Whether the operation is a move or copy. */
1174 svn_boolean_t is_move;
1177 static svn_error_t *
1178 path_driver_cb_func(void **dir_baton,
1180 void *callback_baton,
1184 struct path_driver_cb_baton *cb_baton = callback_baton;
1185 svn_boolean_t do_delete = FALSE, do_add = FALSE;
1186 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1188 /* Initialize return value. */
1191 /* This function should never get an empty PATH. We can neither
1192 create nor delete the empty PATH, so if someone is calling us
1193 with such, the code is just plain wrong. */
1194 SVN_ERR_ASSERT(! svn_path_is_empty(path));
1196 /* Check to see if we need to add the path as a parent directory. */
1197 if (path_info->dir_add)
1199 return cb_baton->editor->add_directory(path, parent_baton, NULL,
1200 SVN_INVALID_REVNUM, pool,
1204 /* If this is a resurrection, we know the source and dest paths are
1205 the same, and that our driver will only be calling us once. */
1206 if (path_info->resurrection)
1208 /* If this is a move, we do nothing. Otherwise, we do the copy. */
1209 if (! cb_baton->is_move)
1212 /* Not a resurrection. */
1215 /* If this is a move, we check PATH to see if it is the source
1216 or the destination of the move. */
1217 if (cb_baton->is_move)
1219 if (strcmp(path_info->src_path, path) == 0)
1224 /* Not a move? This must just be the copy addition. */
1227 do_add = !path_info->only_pin_externals;
1233 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
1234 parent_baton, pool));
1238 SVN_ERR(svn_path_check_valid(path, pool));
1240 if (path_info->src_kind == svn_node_file)
1243 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
1245 path_info->src_revnum,
1246 pool, &file_baton));
1247 if (path_info->mergeinfo)
1248 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
1250 path_info->mergeinfo,
1252 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
1256 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
1258 path_info->src_revnum,
1260 if (path_info->mergeinfo)
1261 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
1263 path_info->mergeinfo,
1268 if (path_info->externals)
1270 if (*dir_baton == NULL)
1271 SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
1275 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1276 path_info->externals, pool));
1279 return SVN_NO_ERROR;
1283 /* Starting with the path DIR relative to the RA_SESSION's session
1284 URL, work up through DIR's parents until an existing node is found.
1285 Push each nonexistent path onto the array NEW_DIRS, allocating in
1286 POOL. Raise an error if the existing node is not a directory.
1288 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1289 ### implementation susceptible to race conditions. */
1290 static svn_error_t *
1291 find_absent_parents1(svn_ra_session_t *ra_session,
1293 apr_array_header_t *new_dirs,
1296 svn_node_kind_t kind;
1297 apr_pool_t *iterpool = svn_pool_create(pool);
1299 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1302 while (kind == svn_node_none)
1304 svn_pool_clear(iterpool);
1306 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1307 dir = svn_dirent_dirname(dir, pool);
1309 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1313 if (kind != svn_node_dir)
1314 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1315 _("Path '%s' already exists, but is not a "
1318 svn_pool_destroy(iterpool);
1319 return SVN_NO_ERROR;
1322 /* Starting with the URL *TOP_DST_URL which is also the root of
1323 RA_SESSION, work up through its parents until an existing node is
1324 found. Push each nonexistent URL onto the array NEW_DIRS,
1325 allocating in POOL. Raise an error if the existing node is not a
1328 Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1330 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1331 ### implementation susceptible to race conditions. */
1332 static svn_error_t *
1333 find_absent_parents2(svn_ra_session_t *ra_session,
1334 const char **top_dst_url,
1335 apr_array_header_t *new_dirs,
1338 const char *root_url = *top_dst_url;
1339 svn_node_kind_t kind;
1341 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1344 while (kind == svn_node_none)
1346 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1347 root_url = svn_uri_dirname(root_url, pool);
1349 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1350 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1354 if (kind != svn_node_dir)
1355 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1356 _("Path '%s' already exists, but is not a directory"),
1359 *top_dst_url = root_url;
1360 return SVN_NO_ERROR;
1363 /* Queue property changes for pinning svn:externals properties set on
1364 * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1365 * is keyed by the relative path of each descendant which should have some
1366 * or all of its externals pinned, with the corresponding pinned svn:externals
1367 * properties as values. Property changes are queued in a new list of path
1368 * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1369 * existing item is found for the descendant. Allocate results in RESULT_POOL.
1370 * Use SCRATCH_POOL for temporary allocations. */
1371 static svn_error_t *
1372 queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1373 apr_array_header_t *path_infos,
1374 apr_hash_t *pinned_externals,
1375 path_driver_info_t *parent_info,
1376 apr_pool_t *result_pool,
1377 apr_pool_t *scratch_pool)
1379 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1380 apr_hash_index_t *hi;
1382 for (hi = apr_hash_first(scratch_pool, pinned_externals);
1384 hi = apr_hash_next(hi))
1386 const char *dst_relpath = apr_hash_this_key(hi);
1387 svn_string_t *externals_prop = apr_hash_this_val(hi);
1388 const char *src_url;
1389 path_driver_info_t *info;
1392 svn_pool_clear(iterpool);
1394 src_url = svn_path_url_add_component2(parent_info->src_url,
1395 dst_relpath, iterpool);
1397 /* Try to find a path info the external change can be applied to. */
1399 for (i = 0; i < path_infos->nelts; i++)
1401 path_driver_info_t *existing_info;
1403 existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1404 if (strcmp(src_url, existing_info->src_url) == 0)
1406 info = existing_info;
1413 /* A copied-along child needs its externals pinned.
1414 Create a new path info for this property change. */
1415 info = apr_pcalloc(result_pool, sizeof(*info));
1416 info->src_url = svn_path_url_add_component2(
1417 parent_info->src_url, dst_relpath,
1419 info->src_path = NULL; /* Only needed on copied dirs */
1420 info->dst_path = svn_relpath_join(parent_info->dst_path,
1423 info->src_kind = svn_node_dir;
1424 info->only_pin_externals = TRUE;
1425 APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1428 info->externals = externals_prop;
1431 svn_pool_destroy(iterpool);
1433 return SVN_NO_ERROR;
1436 static svn_error_t *
1437 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
1438 svn_boolean_t make_parents,
1439 const apr_hash_t *revprop_table,
1440 svn_commit_callback2_t commit_callback,
1442 svn_client_ctx_t *ctx,
1443 svn_boolean_t is_move,
1444 svn_boolean_t pin_externals,
1445 const apr_hash_t *externals_to_pin,
1449 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1450 sizeof(const char *));
1451 apr_hash_t *action_hash = apr_hash_make(pool);
1452 apr_array_header_t *path_infos;
1453 const char *top_url, *top_url_all, *top_url_dst;
1454 const char *message, *repos_root;
1455 svn_ra_session_t *ra_session = NULL;
1456 const svn_delta_editor_t *editor;
1458 struct path_driver_cb_baton cb_baton;
1459 apr_array_header_t *new_dirs = NULL;
1460 apr_hash_t *commit_revprops;
1461 apr_array_header_t *pin_externals_only_infos = NULL;
1463 svn_client__copy_pair_t *first_pair =
1464 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1466 /* Open an RA session to the first copy pair's destination. We'll
1467 be verifying that every one of our copy source and destination
1468 URLs is or is beneath this sucker's repository root URL as a form
1469 of a cheap(ish) sanity check. */
1470 SVN_ERR(svn_client_open_ra_session2(&ra_session,
1471 first_pair->src_abspath_or_url, NULL,
1473 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1475 /* Verify that sources and destinations are all at or under
1476 REPOS_ROOT. While here, create a path_info struct for each
1477 src/dst pair and initialize portions of it with normalized source
1478 location information. */
1479 path_infos = apr_array_make(pool, copy_pairs->nelts,
1480 sizeof(path_driver_info_t *));
1481 for (i = 0; i < copy_pairs->nelts; i++)
1483 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1484 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1485 svn_client__copy_pair_t *);
1486 apr_hash_t *mergeinfo;
1488 /* Are the source and destination URLs at or under REPOS_ROOT? */
1489 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1490 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1491 return svn_error_create
1492 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1493 _("Source and destination URLs appear not to point to the "
1494 "same repository."));
1496 /* Run the history function to get the source's URL and revnum in the
1497 operational revision. */
1498 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1499 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1503 pair->src_abspath_or_url,
1504 &pair->src_peg_revision,
1505 &pair->src_op_revision, NULL,
1508 /* Go ahead and grab mergeinfo from the source, too. */
1509 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1510 SVN_ERR(svn_client__get_repos_mergeinfo(
1511 &mergeinfo, ra_session,
1512 pair->src_abspath_or_url, pair->src_revnum,
1513 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1515 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1517 /* Plop an INFO structure onto our array thereof. */
1518 info->src_url = pair->src_abspath_or_url;
1519 info->src_revnum = pair->src_revnum;
1520 info->resurrection = FALSE;
1521 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1524 /* If this is a move, we have to open our session to the longest
1525 path common to all SRC_URLS and DST_URLS in the repository so we
1526 can do existence checks on all paths, and so we can operate on
1527 all paths in the case of a move. But if this is *not* a move,
1528 then opening our session at the longest path common to sources
1529 *and* destinations might be an optimization when the user is
1530 authorized to access all that stuff, but could cause the
1531 operation to fail altogether otherwise. See issue #3242. */
1532 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1534 top_url = is_move ? top_url_all : top_url_dst;
1536 /* Check each src/dst pair for resurrection, and verify that TOP_URL
1537 is anchored high enough to cover all the editor_t activities
1538 required for this operation. */
1539 for (i = 0; i < copy_pairs->nelts; i++)
1541 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1542 svn_client__copy_pair_t *);
1543 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1544 path_driver_info_t *);
1546 /* Source and destination are the same? It's a resurrection. */
1547 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1548 info->resurrection = TRUE;
1550 /* We need to add each dst_URL, and (in a move) we'll need to
1551 delete each src_URL. Our selection of TOP_URL so far ensures
1552 that all our destination URLs (and source URLs, for moves)
1553 are at least as deep as TOP_URL, but we need to make sure
1554 that TOP_URL is an *ancestor* of all our to-be-edited paths.
1556 Issue #683 is demonstrates this scenario. If you're
1557 resurrecting a deleted item like this: 'svn cp -rN src_URL
1558 dst_URL', then src_URL == dst_URL == top_url. In this
1559 situation, we want to open an RA session to be at least the
1560 *parent* of all three. */
1561 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1562 && (strcmp(top_url, repos_root) != 0))
1564 top_url = svn_uri_dirname(top_url, pool);
1567 && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1568 && (strcmp(top_url, repos_root) != 0))
1570 top_url = svn_uri_dirname(top_url, pool);
1574 /* Point the RA session to our current TOP_URL. */
1575 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1577 /* If we're allowed to create nonexistent parent directories of our
1578 destinations, then make a list in NEW_DIRS of the parent
1579 directories of the destination that don't yet exist. */
1582 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1584 /* If this is a move, TOP_URL is at least the common ancestor of
1585 all the paths (sources and destinations) involved. Assuming
1586 the sources exist (which is fair, because if they don't, this
1587 whole operation will fail anyway), TOP_URL must also exist.
1588 So it's the paths between TOP_URL and the destinations which
1589 we have to check for existence. But here, we take advantage
1590 of the knowledge of our caller. We know that if there are
1591 multiple copy/move operations being requested, then the
1592 destinations of the copies/moves will all be siblings of one
1593 another. Therefore, we need only to check for the
1594 nonexistent paths between TOP_URL and *one* of our
1595 destinations to find nonexistent parents of all of them. */
1598 /* Imagine a situation where the user tries to copy an
1599 existing source directory to nonexistent directory with
1600 --parents options specified:
1602 svn copy --parents URL/src URL/dst
1604 where src exists and dst does not. If the dirname of the
1605 destination path is equal to TOP_URL,
1606 do not try to add dst to the NEW_DIRS list since it
1607 will be added to the commit items array later in this
1609 const char *dir = svn_uri_skip_ancestor(
1611 svn_uri_dirname(first_pair->dst_abspath_or_url,
1615 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1617 /* If, however, this is *not* a move, TOP_URL only points to the
1618 common ancestor of our destination path(s), or possibly one
1619 level higher. We'll need to do an existence crawl toward the
1620 root of the repository, starting with one of our destinations
1621 (see "... take advantage of the knowledge of our caller ..."
1622 above), and possibly adjusting TOP_URL as we go. */
1625 apr_array_header_t *new_urls =
1626 apr_array_make(pool, 0, sizeof(const char *));
1627 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1629 /* Convert absolute URLs into relpaths relative to TOP_URL. */
1630 for (i = 0; i < new_urls->nelts; i++)
1632 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1633 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1635 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1640 /* For each src/dst pair, check to see if that SRC_URL is a child of
1641 the DST_URL (excepting the case where DST_URL is the repo root).
1642 If it is, and the parent of DST_URL is the current TOP_URL, then we
1643 need to reparent the session one directory higher, the parent of
1645 for (i = 0; i < copy_pairs->nelts; i++)
1647 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1648 svn_client__copy_pair_t *);
1649 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1650 path_driver_info_t *);
1651 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1652 pair->src_abspath_or_url,
1655 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1656 && (relpath != NULL && *relpath != '\0'))
1658 info->resurrection = TRUE;
1659 top_url = svn_uri_get_longest_ancestor(
1661 svn_uri_dirname(pair->dst_abspath_or_url, pool),
1663 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1667 /* Get the portions of the SRC and DST URLs that are relative to
1668 TOP_URL (URI-decoding them while we're at it), verify that the
1669 source exists and the proposed destination does not, and toss
1670 what we've learned into the INFO array. (For copies -- that is,
1671 non-moves -- the relative source URL NULL because it isn't a
1672 child of the TOP_URL at all. That's okay, we'll deal with
1674 for (i = 0; i < copy_pairs->nelts; i++)
1676 svn_client__copy_pair_t *pair =
1677 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1678 path_driver_info_t *info =
1679 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1680 svn_node_kind_t dst_kind;
1681 const char *src_rel, *dst_rel;
1683 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1686 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1687 &info->src_kind, pool));
1691 const char *old_url;
1694 SVN_ERR_ASSERT(! is_move);
1696 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1697 pair->src_abspath_or_url,
1699 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1700 &info->src_kind, pool));
1701 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1703 if (info->src_kind == svn_node_none)
1704 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1705 _("Path '%s' does not exist in revision %ld"),
1706 pair->src_abspath_or_url, pair->src_revnum);
1708 /* Figure out the basename that will result from this operation,
1709 and ensure that we aren't trying to overwrite existing paths. */
1710 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1711 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1713 if (dst_kind != svn_node_none)
1714 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1715 _("Path '%s' already exists"),
1716 pair->dst_abspath_or_url);
1718 /* More info for our INFO structure. */
1719 info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1720 info->dst_path = dst_rel;
1722 svn_hash_sets(action_hash, info->dst_path, info);
1723 if (is_move && (! info->resurrection))
1724 svn_hash_sets(action_hash, info->src_path, info);
1728 apr_hash_t *pinned_externals;
1730 SVN_ERR(resolve_pinned_externals(&pinned_externals,
1731 externals_to_pin, pair,
1732 ra_session, repos_root,
1734 if (pin_externals_only_infos == NULL)
1736 pin_externals_only_infos =
1737 apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1739 SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1746 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1748 /* Produce a list of new paths to add, and provide it to the
1749 mechanism used to acquire a log message. */
1750 svn_client_commit_item3_t *item;
1751 const char *tmp_file;
1752 apr_array_header_t *commit_items
1753 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1755 /* Add any intermediate directories to the message */
1758 for (i = 0; i < new_dirs->nelts; i++)
1760 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1762 item = svn_client_commit_item3_create(pool);
1763 item->url = svn_path_url_add_component2(top_url, relpath, pool);
1764 item->kind = svn_node_dir;
1765 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1766 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1770 for (i = 0; i < path_infos->nelts; i++)
1772 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1773 path_driver_info_t *);
1775 item = svn_client_commit_item3_create(pool);
1776 item->url = svn_path_url_add_component2(top_url, info->dst_path,
1778 item->kind = info->src_kind;
1779 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1780 | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1781 item->copyfrom_url = info->src_url;
1782 item->copyfrom_rev = info->src_revnum;
1783 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1785 if (is_move && (! info->resurrection))
1787 item = svn_client_commit_item3_create(pool);
1788 item->url = svn_path_url_add_component2(top_url, info->src_path,
1790 item->kind = info->src_kind;
1791 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1792 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1796 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1799 return SVN_NO_ERROR;
1804 /* Setup our PATHS for the path-based editor drive. */
1805 /* First any intermediate directories. */
1808 for (i = 0; i < new_dirs->nelts; i++)
1810 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1811 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1813 info->dst_path = relpath;
1814 info->dir_add = TRUE;
1816 APR_ARRAY_PUSH(paths, const char *) = relpath;
1817 svn_hash_sets(action_hash, relpath, info);
1821 /* Then our copy destinations and move sources (if any). */
1822 for (i = 0; i < path_infos->nelts; i++)
1824 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1825 path_driver_info_t *);
1827 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1828 if (is_move && (! info->resurrection))
1829 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1832 /* Add any items which only need their externals pinned. */
1833 if (pin_externals_only_infos)
1835 for (i = 0; i < pin_externals_only_infos->nelts; i++)
1837 path_driver_info_t *info;
1839 info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1840 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1841 svn_hash_sets(action_hash, info->dst_path, info);
1845 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1846 message, ctx, pool));
1848 /* Fetch RA commit editor. */
1849 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1850 svn_client__get_shim_callbacks(ctx->wc_ctx,
1852 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1856 NULL, TRUE, /* No lock tokens */
1859 /* Setup the callback baton. */
1860 cb_baton.editor = editor;
1861 cb_baton.edit_baton = edit_baton;
1862 cb_baton.action_hash = action_hash;
1863 cb_baton.is_move = is_move;
1865 /* Call the path-based editor driver. */
1866 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1867 path_driver_cb_func, &cb_baton, pool);
1870 /* At least try to abort the edit (and fs txn) before throwing err. */
1871 return svn_error_compose_create(
1873 editor->abort_edit(edit_baton, pool));
1876 if (ctx->notify_func2)
1878 svn_wc_notify_t *notify;
1879 notify = svn_wc_create_notify_url(top_url,
1880 svn_wc_notify_commit_finalizing,
1882 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1885 /* Close the edit. */
1886 return svn_error_trace(editor->close_edit(edit_baton, pool));
1889 /* Baton for check_url_kind */
1890 struct check_url_kind_baton
1892 svn_ra_session_t *session;
1893 const char *repos_root_url;
1894 svn_boolean_t should_reparent;
1897 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1898 static svn_error_t *
1899 check_url_kind(void *baton,
1900 svn_node_kind_t *kind,
1902 svn_revnum_t revision,
1903 apr_pool_t *scratch_pool)
1905 struct check_url_kind_baton *cukb = baton;
1907 /* If we don't have a session or can't use the session, get one */
1908 if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1909 *kind = svn_node_none;
1912 cukb->should_reparent = TRUE;
1914 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1916 SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1917 kind, scratch_pool));
1920 return SVN_NO_ERROR;
1923 /* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1924 * in the COMMIT_ITEMS list.
1925 * If the list does not already have a commit item for COMMIT_URL
1926 * add a new commit item for the property change.
1927 * Allocate results in RESULT_POOL.
1928 * Use SCRATCH_POOL for temporary allocations. */
1929 static svn_error_t *
1930 queue_prop_change_commit_items(const char *local_abspath,
1931 const char *commit_url,
1932 apr_array_header_t *commit_items,
1933 const char *propname,
1934 svn_string_t *propval,
1935 apr_pool_t *result_pool,
1936 apr_pool_t *scratch_pool)
1938 svn_client_commit_item3_t *item = NULL;
1942 for (i = 0; i < commit_items->nelts; i++)
1944 svn_client_commit_item3_t *existing_item;
1946 existing_item = APR_ARRAY_IDX(commit_items, i,
1947 svn_client_commit_item3_t *);
1948 if (strcmp(existing_item->url, commit_url) == 0)
1950 item = existing_item;
1957 item = svn_client_commit_item3_create(result_pool);
1958 item->path = local_abspath;
1959 item->url = commit_url;
1960 item->kind = svn_node_dir;
1961 item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1963 item->incoming_prop_changes = apr_array_make(result_pool, 1,
1964 sizeof(svn_prop_t *));
1965 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1968 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1970 if (item->outgoing_prop_changes == NULL)
1971 item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1972 sizeof(svn_prop_t *));
1974 prop = apr_palloc(result_pool, sizeof(*prop));
1975 prop->name = propname;
1976 prop->value = propval;
1977 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1979 return SVN_NO_ERROR;
1983 * COMMIT_INFO_P is ...
1984 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1985 * and each 'dst_abspath_or_url' is a URL.
1986 * MAKE_PARENTS is ...
1987 * REVPROP_TABLE is ...
1989 static svn_error_t *
1990 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1991 svn_boolean_t make_parents,
1992 const apr_hash_t *revprop_table,
1993 svn_commit_callback2_t commit_callback,
1995 svn_boolean_t pin_externals,
1996 const apr_hash_t *externals_to_pin,
1997 svn_client_ctx_t *ctx,
1998 apr_pool_t *scratch_pool)
2000 const char *message;
2001 const char *top_src_path, *top_dst_url;
2002 struct check_url_kind_baton cukb;
2003 const char *top_src_abspath;
2004 svn_ra_session_t *ra_session;
2005 const svn_delta_editor_t *editor;
2006 #ifdef ENABLE_EV2_SHIMS
2007 apr_hash_t *relpath_map = NULL;
2010 svn_client__committables_t *committables;
2011 apr_array_header_t *commit_items;
2012 apr_pool_t *iterpool;
2013 apr_array_header_t *new_dirs = NULL;
2014 apr_hash_t *commit_revprops;
2015 svn_client__copy_pair_t *first_pair;
2016 apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2017 apr_array_header_t *commit_items_for_dav;
2020 /* Find the common root of all the source paths */
2021 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2024 /* Do we need to lock the working copy? 1.6 didn't take a write
2025 lock, but what happens if the working copy changes during the copy
2028 iterpool = svn_pool_create(scratch_pool);
2030 /* Determine the longest common ancestor for the destinations, and open an RA
2031 session to that location. */
2032 /* ### But why start by getting the _parent_ of the first one? */
2033 /* --- That works because multiple destinations always point to the same
2034 * directory. I'm rather wondering why we need to find a common
2035 * destination parent here at all, instead of simply getting
2036 * top_dst_url from get_copy_pair_ancestors() above?
2037 * It looks like the entire block of code hanging off this comment
2039 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2040 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2041 for (i = 1; i < copy_pairs->nelts; i++)
2043 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2044 svn_client__copy_pair_t *);
2045 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2046 pair->dst_abspath_or_url,
2050 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2052 commit_items_for_dav = apr_array_make(session_pool, 0,
2053 sizeof(svn_client_commit_item3_t*));
2055 /* Open a session to help while determining the exact targets */
2056 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2058 commit_items_for_dav,
2059 FALSE /* write_dav_props */,
2060 TRUE /* read_dav_props */,
2062 session_pool, session_pool));
2064 /* If requested, determine the nearest existing parent of the destination,
2065 and reparent the ra session there. */
2068 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2069 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2073 /* Figure out the basename that will result from each copy and check to make
2074 sure it doesn't exist already. */
2075 for (i = 0; i < copy_pairs->nelts; i++)
2077 svn_node_kind_t dst_kind;
2078 const char *dst_rel;
2079 svn_client__copy_pair_t *pair =
2080 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2082 svn_pool_clear(iterpool);
2083 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2085 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2086 &dst_kind, iterpool));
2087 if (dst_kind != svn_node_none)
2089 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2090 _("Path '%s' already exists"),
2091 pair->dst_abspath_or_url);
2095 cukb.session = ra_session;
2096 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2097 cukb.should_reparent = FALSE;
2099 /* Crawl the working copy for commit items. */
2100 /* ### TODO: Pass check_url_func for issue #3314 handling */
2101 SVN_ERR(svn_client__get_copy_committables(&committables,
2103 check_url_kind, &cukb,
2104 ctx, scratch_pool, iterpool));
2106 /* The committables are keyed by the repository root */
2107 commit_items = svn_hash_gets(committables->by_repository,
2108 cukb.repos_root_url);
2109 SVN_ERR_ASSERT(commit_items != NULL);
2111 if (cukb.should_reparent)
2112 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2114 /* If we are creating intermediate directories, tack them onto the list
2118 for (i = 0; i < new_dirs->nelts; i++)
2120 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2121 svn_client_commit_item3_t *item;
2123 item = svn_client_commit_item3_create(scratch_pool);
2125 item->kind = svn_node_dir;
2126 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2127 item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2128 sizeof(svn_prop_t *));
2129 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2133 /* ### TODO: This extra loop would be unnecessary if this code lived
2134 ### in svn_client__get_copy_committables(), which is incidentally
2135 ### only used above (so should really be in this source file). */
2136 for (i = 0; i < copy_pairs->nelts; i++)
2138 apr_hash_t *mergeinfo, *wc_mergeinfo;
2139 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2140 svn_client__copy_pair_t *);
2141 svn_client_commit_item3_t *item =
2142 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2143 svn_client__pathrev_t *src_origin;
2145 svn_pool_clear(iterpool);
2147 SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2148 pair->src_abspath_or_url,
2149 ctx, iterpool, iterpool));
2151 /* Set the mergeinfo for the destination to the combined merge
2152 info known to the WC and the repository. */
2153 /* Repository mergeinfo (or NULL if it's locally added)... */
2155 SVN_ERR(svn_client__get_repos_mergeinfo(
2156 &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2157 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2160 /* ... and WC mergeinfo. */
2161 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2162 pair->src_abspath_or_url,
2163 iterpool, iterpool));
2164 if (wc_mergeinfo && mergeinfo)
2165 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2167 else if (! mergeinfo)
2168 mergeinfo = wc_mergeinfo;
2172 /* Push a mergeinfo prop representing MERGEINFO onto the
2173 * OUTGOING_PROP_CHANGES array. */
2175 svn_prop_t *mergeinfo_prop
2176 = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2177 svn_string_t *prop_value;
2179 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2182 if (!item->outgoing_prop_changes)
2184 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2185 sizeof(svn_prop_t *));
2188 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2189 mergeinfo_prop->value = prop_value;
2190 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2196 apr_hash_t *pinned_externals;
2197 apr_hash_index_t *hi;
2199 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2200 externals_to_pin, pair,
2201 ra_session, cukb.repos_root_url,
2202 ctx, scratch_pool, iterpool));
2203 for (hi = apr_hash_first(scratch_pool, pinned_externals);
2205 hi = apr_hash_next(hi))
2207 const char *dst_relpath = apr_hash_this_key(hi);
2208 svn_string_t *externals_propval = apr_hash_this_val(hi);
2209 const char *dst_url;
2210 const char *commit_url;
2211 const char *src_abspath;
2213 if (svn_path_is_url(pair->dst_abspath_or_url))
2214 dst_url = pair->dst_abspath_or_url;
2216 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2217 pair->dst_abspath_or_url,
2218 scratch_pool, iterpool));
2219 commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2221 src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2222 dst_relpath, iterpool);
2223 SVN_ERR(queue_prop_change_commit_items(src_abspath,
2224 commit_url, commit_items,
2227 scratch_pool, iterpool));
2232 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2234 const char *tmp_file;
2236 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2237 ctx, scratch_pool));
2240 svn_pool_destroy(iterpool);
2241 svn_pool_destroy(session_pool);
2242 return SVN_NO_ERROR;
2248 /* Sort and condense our COMMIT_ITEMS. */
2249 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2250 commit_items, scratch_pool));
2252 /* Add the commit items to the DAV commit item list to provide access
2253 to dav properties (for pre http-v2 DAV) */
2254 apr_array_cat(commit_items_for_dav, commit_items);
2256 #ifdef ENABLE_EV2_SHIMS
2259 relpath_map = apr_hash_make(scratch_pool);
2260 for (i = 0; i < commit_items->nelts; i++)
2262 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2263 svn_client_commit_item3_t *);
2264 const char *relpath;
2269 svn_pool_clear(iterpool);
2270 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2272 ctx->wc_ctx, item->path, FALSE,
2273 scratch_pool, iterpool));
2275 svn_hash_sets(relpath_map, relpath, item->path);
2280 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2282 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2283 message, ctx, session_pool));
2285 /* Fetch RA commit editor. */
2286 #ifdef ENABLE_EV2_SHIMS
2287 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2288 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2291 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2295 TRUE, /* No lock tokens */
2298 /* Perform the commit. */
2299 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2301 NULL /* notify_path_prefix */,
2302 NULL, ctx, session_pool, session_pool),
2303 _("Commit failed (details follow):"));
2305 svn_pool_destroy(iterpool);
2306 svn_pool_destroy(session_pool);
2308 return SVN_NO_ERROR;
2311 /* A baton for notification_adjust_func(). */
2312 struct notification_adjust_baton
2314 svn_wc_notify_func2_t inner_func;
2316 const char *checkout_abspath;
2317 const char *final_abspath;
2320 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2321 * baton is BATON->inner_baton) and adjusts the notification paths that
2322 * start with BATON->checkout_abspath to start instead with
2323 * BATON->final_abspath. */
2325 notification_adjust_func(void *baton,
2326 const svn_wc_notify_t *notify,
2329 struct notification_adjust_baton *nb = baton;
2330 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2331 const char *relpath;
2333 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2334 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2337 nb->inner_func(nb->inner_baton, inner_notify, pool);
2340 /* Peform each individual copy operation for a repos -> wc copy. A
2341 helper for repos_to_wc_copy().
2343 Resolve PAIR->src_revnum to a real revision number if it isn't already. */
2344 static svn_error_t *
2345 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2346 svn_client__copy_pair_t *pair,
2347 svn_boolean_t same_repositories,
2348 svn_boolean_t ignore_externals,
2349 svn_boolean_t pin_externals,
2350 const apr_hash_t *externals_to_pin,
2351 svn_ra_session_t *ra_session,
2352 svn_client_ctx_t *ctx,
2355 apr_hash_t *src_mergeinfo;
2356 const char *dst_abspath = pair->dst_abspath_or_url;
2358 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2360 if (!same_repositories && ctx->notify_func2)
2362 svn_wc_notify_t *notify;
2363 notify = svn_wc_create_notify_url(
2364 pair->src_abspath_or_url,
2365 svn_wc_notify_foreign_copy_begin,
2367 notify->kind = pair->src_kind;
2368 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2370 /* Allow a theoretical cancel to get through. */
2371 if (ctx->cancel_func)
2372 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2375 if (pair->src_kind == svn_node_dir)
2377 if (same_repositories)
2379 const char *tmpdir_abspath, *tmp_abspath;
2381 /* Find a temporary location in which to check out the copy source. */
2382 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2385 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2386 svn_io_file_del_on_close, pool, pool));
2388 /* Make a new checkout of the requested source. While doing so,
2389 * resolve pair->src_revnum to an actual revision number in case it
2390 * was until now 'invalid' meaning 'head'. Ask this function not to
2391 * sleep for timestamps, by passing a sleep_needed output param.
2392 * Send notifications for all nodes except the root node, and adjust
2393 * them to refer to the destination rather than this temporary path. */
2395 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2396 void *old_notify_baton2 = ctx->notify_baton2;
2397 struct notification_adjust_baton nb;
2400 nb.inner_func = ctx->notify_func2;
2401 nb.inner_baton = ctx->notify_baton2;
2402 nb.checkout_abspath = tmp_abspath;
2403 nb.final_abspath = dst_abspath;
2404 ctx->notify_func2 = notification_adjust_func;
2405 ctx->notify_baton2 = &nb;
2407 /* Avoid a chicken-and-egg problem:
2408 * If pinning externals we'll need to adjust externals
2409 * properties before checking out any externals.
2410 * But copy needs to happen before pinning because else there
2411 * are no svn:externals properties to pin. */
2413 ignore_externals = TRUE;
2415 err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
2418 &pair->src_peg_revision,
2419 &pair->src_op_revision,
2421 ignore_externals, FALSE,
2422 ra_session, ctx, pool);
2424 ctx->notify_func2 = old_notify_func2;
2425 ctx->notify_baton2 = old_notify_baton2;
2430 *timestamp_sleep = TRUE;
2432 /* Schedule dst_path for addition in parent, with copy history.
2433 Don't send any notification here.
2434 Then remove the temporary checkout's .svn dir in preparation for
2435 moving the rest of it into the final destination. */
2436 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2437 TRUE /* metadata_only */,
2438 ctx->cancel_func, ctx->cancel_baton,
2440 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2441 FALSE, pool, pool));
2442 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2449 /* Move the temporary disk tree into place. */
2450 SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, pool));
2454 *timestamp_sleep = TRUE;
2456 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
2458 &pair->src_peg_revision,
2459 &pair->src_op_revision,
2461 FALSE /* make_parents */,
2462 TRUE /* already_locked */,
2465 return SVN_NO_ERROR;
2470 apr_hash_t *pinned_externals;
2471 apr_hash_index_t *hi;
2472 apr_pool_t *iterpool;
2473 const char *repos_root_url;
2474 apr_hash_t *new_externals;
2475 apr_hash_t *new_depths;
2477 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2478 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2479 externals_to_pin, pair,
2480 ra_session, repos_root_url,
2483 iterpool = svn_pool_create(pool);
2484 for (hi = apr_hash_first(pool, pinned_externals);
2486 hi = apr_hash_next(hi))
2488 const char *dst_relpath = apr_hash_this_key(hi);
2489 svn_string_t *externals_propval = apr_hash_this_val(hi);
2490 const char *local_abspath;
2492 svn_pool_clear(iterpool);
2494 local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2495 dst_relpath, iterpool);
2496 /* ### use a work queue? */
2497 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2498 SVN_PROP_EXTERNALS, externals_propval,
2499 svn_depth_empty, TRUE /* skip_checks */,
2500 NULL /* changelist_filter */,
2501 ctx->cancel_func, ctx->cancel_baton,
2502 NULL, NULL, /* no extra notification */
2506 /* Now update all externals in the newly created copy. */
2507 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2512 iterpool, iterpool));
2513 SVN_ERR(svn_client__handle_externals(new_externals,
2515 repos_root_url, dst_abspath,
2520 svn_pool_destroy(iterpool);
2522 } /* end directory case */
2524 else if (pair->src_kind == svn_node_file)
2526 apr_hash_t *new_props;
2527 const char *src_rel;
2528 svn_stream_t *new_base_contents = svn_stream_buffered(pool);
2530 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2531 pair->src_abspath_or_url,
2533 /* Fetch the file content. While doing so, resolve pair->src_revnum
2534 * to an actual revision number if it's 'invalid' meaning 'head'. */
2535 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
2537 &pair->src_revnum, &new_props, pool));
2539 if (new_props && ! same_repositories)
2540 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2542 *timestamp_sleep = TRUE;
2544 SVN_ERR(svn_wc_add_repos_file4(
2545 ctx->wc_ctx, dst_abspath,
2546 new_base_contents, NULL, new_props, NULL,
2547 same_repositories ? pair->src_abspath_or_url : NULL,
2548 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
2549 ctx->cancel_func, ctx->cancel_baton,
2553 /* Record the implied mergeinfo (before the notification callback
2554 is invoked for the root node). */
2555 SVN_ERR(svn_client__get_repos_mergeinfo(
2556 &src_mergeinfo, ra_session,
2557 pair->src_abspath_or_url, pair->src_revnum,
2558 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
2559 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2561 /* Do our own notification for the root node, even if we could possibly
2562 have delegated it. See also issue #1552.
2564 ### Maybe this notification should mention the mergeinfo change. */
2565 if (ctx->notify_func2)
2567 svn_wc_notify_t *notify = svn_wc_create_notify(
2568 dst_abspath, svn_wc_notify_add, pool);
2569 notify->kind = pair->src_kind;
2570 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2573 return SVN_NO_ERROR;
2576 static svn_error_t *
2577 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2578 const apr_array_header_t *copy_pairs,
2579 const char *top_dst_abspath,
2580 svn_boolean_t ignore_externals,
2581 svn_boolean_t pin_externals,
2582 const apr_hash_t *externals_to_pin,
2583 svn_ra_session_t *ra_session,
2584 svn_client_ctx_t *ctx,
2585 apr_pool_t *scratch_pool)
2588 svn_boolean_t same_repositories;
2589 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2591 /* We've already checked for physical obstruction by a working file.
2592 But there could also be logical obstruction by an entry whose
2593 working file happens to be missing.*/
2594 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
2595 ctx, scratch_pool, iterpool));
2597 /* Decide whether the two repositories are the same or not. */
2599 const char *parent_abspath;
2600 const char *src_uuid, *dst_uuid;
2602 /* Get the repository uuid of SRC_URL */
2603 SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, iterpool));
2605 /* Get repository uuid of dst's parent directory, since dst may
2606 not exist. ### TODO: we should probably walk up the wc here,
2607 in case the parent dir has an imaginary URL. */
2608 if (copy_pairs->nelts == 1)
2609 parent_abspath = svn_dirent_dirname(top_dst_abspath, scratch_pool);
2611 parent_abspath = top_dst_abspath;
2613 SVN_ERR(svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
2614 parent_abspath, ctx,
2615 iterpool, iterpool));
2616 /* ### Also check repos_root_url? */
2617 same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
2620 /* Perform the move for each of the copy_pairs. */
2621 for (i = 0; i < copy_pairs->nelts; i++)
2623 /* Check for cancellation */
2624 if (ctx->cancel_func)
2625 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2627 svn_pool_clear(iterpool);
2629 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2630 APR_ARRAY_IDX(copy_pairs, i,
2631 svn_client__copy_pair_t *),
2634 pin_externals, externals_to_pin,
2635 ra_session, ctx, iterpool));
2637 svn_pool_destroy(iterpool);
2639 return SVN_NO_ERROR;
2642 static svn_error_t *
2643 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2644 const apr_array_header_t *copy_pairs,
2645 svn_boolean_t make_parents,
2646 svn_boolean_t ignore_externals,
2647 svn_boolean_t pin_externals,
2648 const apr_hash_t *externals_to_pin,
2649 svn_client_ctx_t *ctx,
2652 svn_ra_session_t *ra_session;
2653 const char *top_src_url, *top_dst_abspath;
2654 apr_pool_t *iterpool = svn_pool_create(pool);
2655 const char *lock_abspath;
2658 /* Get the real path for the source, based upon its peg revision. */
2659 for (i = 0; i < copy_pairs->nelts; i++)
2661 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2662 svn_client__copy_pair_t *);
2665 svn_pool_clear(iterpool);
2667 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2669 pair->src_abspath_or_url,
2670 &pair->src_peg_revision,
2671 &pair->src_op_revision, NULL,
2674 pair->src_original = pair->src_abspath_or_url;
2675 pair->src_abspath_or_url = apr_pstrdup(pool, src);
2678 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_abspath,
2680 lock_abspath = top_dst_abspath;
2681 if (copy_pairs->nelts == 1)
2683 top_src_url = svn_uri_dirname(top_src_url, pool);
2684 lock_abspath = svn_dirent_dirname(top_dst_abspath, pool);
2687 /* Open a repository session to the longest common src ancestor. We do not
2688 (yet) have a working copy, so we don't have a corresponding path and
2689 tempfiles cannot go into the admin area. */
2690 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2693 /* Get the correct src path for the peg revision used, and verify that we
2694 aren't overwriting an existing path. */
2695 for (i = 0; i < copy_pairs->nelts; i++)
2697 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2698 svn_client__copy_pair_t *);
2699 svn_node_kind_t dst_parent_kind, dst_kind;
2700 const char *dst_parent;
2701 const char *src_rel;
2703 svn_pool_clear(iterpool);
2705 /* Next, make sure that the path exists in the repository. */
2706 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2707 pair->src_abspath_or_url,
2709 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2710 &pair->src_kind, pool));
2711 if (pair->src_kind == svn_node_none)
2713 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2714 return svn_error_createf
2715 (SVN_ERR_FS_NOT_FOUND, NULL,
2716 _("Path '%s' not found in revision %ld"),
2717 pair->src_abspath_or_url, pair->src_revnum);
2719 return svn_error_createf
2720 (SVN_ERR_FS_NOT_FOUND, NULL,
2721 _("Path '%s' not found in head revision"),
2722 pair->src_abspath_or_url);
2725 /* Figure out about dst. */
2726 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
2728 if (dst_kind != svn_node_none)
2730 return svn_error_createf(
2731 SVN_ERR_ENTRY_EXISTS, NULL,
2732 _("Path '%s' already exists"),
2733 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2736 /* Make sure the destination parent is a directory and produce a clear
2737 error message if it is not. */
2738 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
2739 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
2740 if (make_parents && dst_parent_kind == svn_node_none)
2742 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
2745 else if (dst_parent_kind != svn_node_dir)
2747 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
2748 _("Path '%s' is not a directory"),
2749 svn_dirent_local_style(dst_parent, pool));
2752 svn_pool_destroy(iterpool);
2754 SVN_WC__CALL_WITH_WRITE_LOCK(
2755 repos_to_wc_copy_locked(timestamp_sleep,
2756 copy_pairs, top_dst_abspath, ignore_externals,
2757 pin_externals, externals_to_pin,
2758 ra_session, ctx, pool),
2759 ctx->wc_ctx, lock_abspath, FALSE, pool);
2760 return SVN_NO_ERROR;
2763 #define NEED_REPOS_REVNUM(revision) \
2764 ((revision.kind != svn_opt_revision_unspecified) \
2765 && (revision.kind != svn_opt_revision_working))
2769 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2770 * change *TIMESTAMP_SLEEP. This output will be valid even if the
2771 * function returns an error.
2773 * Perform all allocations in POOL.
2775 static svn_error_t *
2776 try_copy(svn_boolean_t *timestamp_sleep,
2777 const apr_array_header_t *sources,
2778 const char *dst_path_in,
2779 svn_boolean_t is_move,
2780 svn_boolean_t allow_mixed_revisions,
2781 svn_boolean_t metadata_only,
2782 svn_boolean_t make_parents,
2783 svn_boolean_t ignore_externals,
2784 svn_boolean_t pin_externals,
2785 const apr_hash_t *externals_to_pin,
2786 const apr_hash_t *revprop_table,
2787 svn_commit_callback2_t commit_callback,
2789 svn_client_ctx_t *ctx,
2792 apr_array_header_t *copy_pairs =
2793 apr_array_make(pool, sources->nelts,
2794 sizeof(svn_client__copy_pair_t *));
2795 svn_boolean_t srcs_are_urls, dst_is_url;
2798 /* Assert instead of crashing if the sources list is empty. */
2799 SVN_ERR_ASSERT(sources->nelts > 0);
2801 /* Are either of our paths URLs? Just check the first src_path. If
2802 there are more than one, we'll check for homogeneity among them
2804 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2805 svn_client_copy_source_t *)->path);
2806 dst_is_url = svn_path_is_url(dst_path_in);
2808 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2810 /* If we have multiple source paths, it implies the dst_path is a
2811 directory we are moving or copying into. Populate the COPY_PAIRS
2812 array to contain a destination path for each of the source paths. */
2813 if (sources->nelts > 1)
2815 apr_pool_t *iterpool = svn_pool_create(pool);
2817 for (i = 0; i < sources->nelts; i++)
2819 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
2820 svn_client_copy_source_t *);
2821 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2822 const char *src_basename;
2823 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2825 svn_pool_clear(iterpool);
2829 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2830 src_basename = svn_uri_basename(pair->src_abspath_or_url,
2835 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2836 source->path, pool));
2837 src_basename = svn_dirent_basename(pair->src_abspath_or_url,
2841 pair->src_op_revision = *source->revision;
2842 pair->src_peg_revision = *source->peg_revision;
2843 pair->src_kind = svn_node_unknown;
2845 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2846 &pair->src_op_revision,
2851 /* Check to see if all the sources are urls or all working copy
2853 if (src_is_url != srcs_are_urls)
2854 return svn_error_create
2855 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2856 _("Cannot mix repository and working copy sources"));
2859 pair->dst_abspath_or_url =
2860 svn_path_url_add_component2(dst_path_in, src_basename, pool);
2862 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2863 src_basename, pool);
2864 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2867 svn_pool_destroy(iterpool);
2871 /* Only one source path. */
2872 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2873 svn_client_copy_source_t *source =
2874 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2875 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2878 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2880 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2881 source->path, pool));
2882 pair->src_op_revision = *source->revision;
2883 pair->src_peg_revision = *source->peg_revision;
2884 pair->src_kind = svn_node_unknown;
2886 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2887 &pair->src_op_revision,
2888 src_is_url, TRUE, pool));
2890 pair->dst_abspath_or_url = dst_path_in;
2891 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2894 if (!srcs_are_urls && !dst_is_url)
2896 apr_pool_t *iterpool = svn_pool_create(pool);
2898 for (i = 0; i < copy_pairs->nelts; i++)
2900 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2901 svn_client__copy_pair_t *);
2903 svn_pool_clear(iterpool);
2905 if (svn_dirent_is_child(pair->src_abspath_or_url,
2906 pair->dst_abspath_or_url, iterpool))
2907 return svn_error_createf
2908 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2909 _("Cannot copy path '%s' into its own child '%s'"),
2910 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2911 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2914 svn_pool_destroy(iterpool);
2917 /* A file external should not be moved since the file external is
2918 implemented as a switched file and it would delete the file the
2919 file external is switched to, which is not the behavior the user
2920 would probably want. */
2921 if (is_move && !srcs_are_urls)
2923 apr_pool_t *iterpool = svn_pool_create(pool);
2925 for (i = 0; i < copy_pairs->nelts; i++)
2927 svn_client__copy_pair_t *pair =
2928 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2929 svn_node_kind_t external_kind;
2930 const char *defining_abspath;
2932 svn_pool_clear(iterpool);
2934 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2935 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2936 NULL, NULL, NULL, ctx->wc_ctx,
2937 pair->src_abspath_or_url,
2938 pair->src_abspath_or_url, TRUE,
2939 iterpool, iterpool));
2941 if (external_kind != svn_node_none)
2942 return svn_error_createf(
2943 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2945 _("Cannot move the external at '%s'; please "
2946 "edit the svn:externals property on '%s'."),
2947 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2948 svn_dirent_local_style(defining_abspath, pool));
2950 svn_pool_destroy(iterpool);
2955 /* Disallow moves between the working copy and the repository. */
2956 if (srcs_are_urls != dst_is_url)
2958 return svn_error_create
2959 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2960 _("Moves between the working copy and the repository are not "
2964 /* Disallow moving any path/URL onto or into itself. */
2965 for (i = 0; i < copy_pairs->nelts; i++)
2967 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2968 svn_client__copy_pair_t *);
2970 if (strcmp(pair->src_abspath_or_url,
2971 pair->dst_abspath_or_url) == 0)
2972 return svn_error_createf(
2973 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2975 _("Cannot move URL '%s' into itself") :
2976 _("Cannot move path '%s' into itself"),
2978 pair->src_abspath_or_url :
2979 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2986 /* If we are doing a wc->* copy, but with an operational revision
2987 other than the working copy revision, we are really doing a
2988 repo->* copy, because we're going to need to get the rev from the
2991 svn_boolean_t need_repos_op_rev = FALSE;
2992 svn_boolean_t need_repos_peg_rev = FALSE;
2994 /* Check to see if any revision is something other than
2995 svn_opt_revision_unspecified or svn_opt_revision_working. */
2996 for (i = 0; i < copy_pairs->nelts; i++)
2998 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2999 svn_client__copy_pair_t *);
3001 if (NEED_REPOS_REVNUM(pair->src_op_revision))
3002 need_repos_op_rev = TRUE;
3004 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3005 need_repos_peg_rev = TRUE;
3007 if (need_repos_op_rev || need_repos_peg_rev)
3011 if (need_repos_op_rev || need_repos_peg_rev)
3013 apr_pool_t *iterpool = svn_pool_create(pool);
3015 for (i = 0; i < copy_pairs->nelts; i++)
3017 const char *copyfrom_repos_root_url;
3018 const char *copyfrom_repos_relpath;
3020 svn_revnum_t copyfrom_rev;
3021 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3022 svn_client__copy_pair_t *);
3024 svn_pool_clear(iterpool);
3026 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3028 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev,
3029 ©from_repos_relpath,
3030 ©from_repos_root_url,
3033 pair->src_abspath_or_url,
3034 TRUE, iterpool, iterpool));
3036 if (copyfrom_repos_relpath)
3037 url = svn_path_url_add_component2(copyfrom_repos_root_url,
3038 copyfrom_repos_relpath,
3041 return svn_error_createf
3042 (SVN_ERR_ENTRY_MISSING_URL, NULL,
3043 _("'%s' does not have a URL associated with it"),
3044 svn_dirent_local_style(pair->src_abspath_or_url, pool));
3046 pair->src_abspath_or_url = url;
3048 if (!need_repos_peg_rev
3049 || pair->src_peg_revision.kind == svn_opt_revision_base)
3051 /* Default the peg revision to that of the WC entry. */
3052 pair->src_peg_revision.kind = svn_opt_revision_number;
3053 pair->src_peg_revision.value.number = copyfrom_rev;
3056 if (pair->src_op_revision.kind == svn_opt_revision_base)
3058 /* Use the entry's revision as the operational rev. */
3059 pair->src_op_revision.kind = svn_opt_revision_number;
3060 pair->src_op_revision.value.number = copyfrom_rev;
3064 svn_pool_destroy(iterpool);
3065 srcs_are_urls = TRUE;
3070 /* Now, call the right handler for the operation. */
3071 if ((! srcs_are_urls) && (! dst_is_url))
3073 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
3074 metadata_only, ctx, pool, pool));
3076 /* Copy or move all targets. */
3078 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3079 copy_pairs, dst_path_in,
3080 allow_mixed_revisions,
3085 /* We ignore these values, so assert the default value */
3086 SVN_ERR_ASSERT(allow_mixed_revisions);
3087 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3095 else if ((! srcs_are_urls) && (dst_is_url))
3097 return svn_error_trace(
3098 wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3099 commit_callback, commit_baton,
3100 pin_externals, externals_to_pin, ctx, pool));
3102 else if ((srcs_are_urls) && (! dst_is_url))
3104 return svn_error_trace(
3105 repos_to_wc_copy(timestamp_sleep,
3106 copy_pairs, make_parents, ignore_externals,
3107 pin_externals, externals_to_pin, ctx, pool));
3111 return svn_error_trace(
3112 repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3113 commit_callback, commit_baton, ctx, is_move,
3114 pin_externals, externals_to_pin, pool));
3120 /* Public Interfaces */
3122 svn_client_copy7(const apr_array_header_t *sources,
3123 const char *dst_path,
3124 svn_boolean_t copy_as_child,
3125 svn_boolean_t make_parents,
3126 svn_boolean_t ignore_externals,
3127 svn_boolean_t metadata_only,
3128 svn_boolean_t pin_externals,
3129 const apr_hash_t *externals_to_pin,
3130 const apr_hash_t *revprop_table,
3131 svn_commit_callback2_t commit_callback,
3133 svn_client_ctx_t *ctx,
3137 svn_boolean_t timestamp_sleep = FALSE;
3138 apr_pool_t *subpool = svn_pool_create(pool);
3140 if (sources->nelts > 1 && !copy_as_child)
3141 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3144 err = try_copy(×tamp_sleep,
3146 FALSE /* is_move */,
3147 TRUE /* allow_mixed_revisions */,
3154 commit_callback, commit_baton,
3158 /* If the destination exists, try to copy the sources as children of the
3160 if (copy_as_child && err && (sources->nelts == 1)
3161 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3162 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3164 const char *src_path = APR_ARRAY_IDX(sources, 0,
3165 svn_client_copy_source_t *)->path;
3166 const char *src_basename;
3167 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3168 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3170 svn_error_clear(err);
3171 svn_pool_clear(subpool);
3173 src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3174 : svn_dirent_basename(src_path, subpool);
3176 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3178 : svn_dirent_join(dst_path, src_basename, subpool);
3180 err = try_copy(×tamp_sleep,
3182 FALSE /* is_move */,
3183 TRUE /* allow_mixed_revisions */,
3190 commit_callback, commit_baton,
3195 /* Sleep if required. DST_PATH is not a URL in these cases. */
3196 if (timestamp_sleep)
3197 svn_io_sleep_for_timestamps(dst_path, subpool);
3199 svn_pool_destroy(subpool);
3200 return svn_error_trace(err);
3205 svn_client_move7(const apr_array_header_t *src_paths,
3206 const char *dst_path,
3207 svn_boolean_t move_as_child,
3208 svn_boolean_t make_parents,
3209 svn_boolean_t allow_mixed_revisions,
3210 svn_boolean_t metadata_only,
3211 const apr_hash_t *revprop_table,
3212 svn_commit_callback2_t commit_callback,
3214 svn_client_ctx_t *ctx,
3217 const svn_opt_revision_t head_revision
3218 = { svn_opt_revision_head, { 0 } };
3220 svn_boolean_t timestamp_sleep = FALSE;
3222 apr_pool_t *subpool = svn_pool_create(pool);
3223 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3224 sizeof(const svn_client_copy_source_t *));
3226 if (src_paths->nelts > 1 && !move_as_child)
3227 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3230 for (i = 0; i < src_paths->nelts; i++)
3232 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3233 svn_client_copy_source_t *copy_source = apr_palloc(pool,
3234 sizeof(*copy_source));
3236 copy_source->path = src_path;
3237 copy_source->revision = &head_revision;
3238 copy_source->peg_revision = &head_revision;
3240 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3243 err = try_copy(×tamp_sleep,
3246 allow_mixed_revisions,
3249 FALSE /* ignore_externals */,
3250 FALSE /* pin_externals */,
3251 NULL /* externals_to_pin */,
3253 commit_callback, commit_baton,
3257 /* If the destination exists, try to move the sources as children of the
3259 if (move_as_child && err && (src_paths->nelts == 1)
3260 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3261 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3263 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3264 const char *src_basename;
3265 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3266 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3268 svn_error_clear(err);
3269 svn_pool_clear(subpool);
3271 src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3272 : svn_dirent_basename(src_path, pool);
3274 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3276 : svn_dirent_join(dst_path, src_basename, subpool);
3278 err = try_copy(×tamp_sleep,
3281 allow_mixed_revisions,
3284 FALSE /* ignore_externals */,
3285 FALSE /* pin_externals */,
3286 NULL /* externals_to_pin */,
3288 commit_callback, commit_baton,
3293 /* Sleep if required. DST_PATH is not a URL in these cases. */
3294 if (timestamp_sleep)
3295 svn_io_sleep_for_timestamps(dst_path, subpool);
3297 svn_pool_destroy(subpool);
3298 return svn_error_trace(err);