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 (make_parents && dst_parent_kind == svn_node_none)
1062 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1063 TRUE, ctx, iterpool));
1065 else if (dst_parent_kind != svn_node_dir)
1067 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1068 _("Path '%s' is not a directory"),
1069 svn_dirent_local_style(
1070 pair->dst_parent_abspath, scratch_pool));
1073 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1074 &dst_parent_kind, scratch_pool));
1076 if (dst_parent_kind != svn_node_dir)
1077 return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
1078 _("Path '%s' is not a directory"),
1079 svn_dirent_local_style(
1080 pair->dst_parent_abspath, scratch_pool));
1083 svn_pool_destroy(iterpool);
1085 return SVN_NO_ERROR;
1088 static svn_error_t *
1089 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
1090 svn_boolean_t make_parents,
1091 svn_boolean_t is_move,
1092 svn_boolean_t metadata_only,
1093 svn_client_ctx_t *ctx,
1094 apr_pool_t *result_pool,
1095 apr_pool_t *scratch_pool)
1098 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1100 /* Check that all of our SRCs exist. */
1101 for (i = 0; i < copy_pairs->nelts; i++)
1103 svn_boolean_t deleted_ok;
1104 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1105 svn_client__copy_pair_t *);
1106 svn_pool_clear(iterpool);
1108 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1109 || pair->src_op_revision.kind == svn_opt_revision_base);
1111 /* Verify that SRC_PATH exists. */
1112 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1113 pair->src_abspath_or_url,
1114 deleted_ok, FALSE, iterpool));
1115 if (pair->src_kind == svn_node_none)
1116 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1117 _("Path '%s' does not exist"),
1118 svn_dirent_local_style(
1119 pair->src_abspath_or_url,
1123 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
1124 result_pool, iterpool));
1126 svn_pool_destroy(iterpool);
1128 return SVN_NO_ERROR;
1132 /* Path-specific state used as part of path_driver_cb_baton. */
1133 typedef struct path_driver_info_t
1135 const char *src_url;
1136 const char *src_path;
1137 const char *dst_path;
1138 svn_node_kind_t src_kind;
1139 svn_revnum_t src_revnum;
1140 svn_boolean_t resurrection;
1141 svn_boolean_t dir_add;
1142 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
1143 svn_string_t *externals; /* new externals definitions for the target */
1144 svn_boolean_t only_pin_externals;
1145 } path_driver_info_t;
1148 /* The baton used with the path_driver_cb_func() callback for a copy
1149 or move operation. */
1150 struct path_driver_cb_baton
1152 /* The editor (and its state) used to perform the operation. */
1153 const svn_delta_editor_t *editor;
1156 /* A hash of path -> path_driver_info_t *'s. */
1157 apr_hash_t *action_hash;
1159 /* Whether the operation is a move or copy. */
1160 svn_boolean_t is_move;
1163 static svn_error_t *
1164 path_driver_cb_func(void **dir_baton,
1166 void *callback_baton,
1170 struct path_driver_cb_baton *cb_baton = callback_baton;
1171 svn_boolean_t do_delete = FALSE, do_add = FALSE;
1172 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1174 /* Initialize return value. */
1177 /* This function should never get an empty PATH. We can neither
1178 create nor delete the empty PATH, so if someone is calling us
1179 with such, the code is just plain wrong. */
1180 SVN_ERR_ASSERT(! svn_path_is_empty(path));
1182 /* Check to see if we need to add the path as a parent directory. */
1183 if (path_info->dir_add)
1185 return cb_baton->editor->add_directory(path, parent_baton, NULL,
1186 SVN_INVALID_REVNUM, pool,
1190 /* If this is a resurrection, we know the source and dest paths are
1191 the same, and that our driver will only be calling us once. */
1192 if (path_info->resurrection)
1194 /* If this is a move, we do nothing. Otherwise, we do the copy. */
1195 if (! cb_baton->is_move)
1198 /* Not a resurrection. */
1201 /* If this is a move, we check PATH to see if it is the source
1202 or the destination of the move. */
1203 if (cb_baton->is_move)
1205 if (strcmp(path_info->src_path, path) == 0)
1210 /* Not a move? This must just be the copy addition. */
1213 do_add = !path_info->only_pin_externals;
1219 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
1220 parent_baton, pool));
1224 SVN_ERR(svn_path_check_valid(path, pool));
1226 if (path_info->src_kind == svn_node_file)
1229 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
1231 path_info->src_revnum,
1232 pool, &file_baton));
1233 if (path_info->mergeinfo)
1234 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
1236 path_info->mergeinfo,
1238 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
1242 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
1244 path_info->src_revnum,
1246 if (path_info->mergeinfo)
1247 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
1249 path_info->mergeinfo,
1254 if (path_info->externals)
1256 if (*dir_baton == NULL)
1257 SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
1261 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1262 path_info->externals, pool));
1265 return SVN_NO_ERROR;
1269 /* Starting with the path DIR relative to the RA_SESSION's session
1270 URL, work up through DIR's parents until an existing node is found.
1271 Push each nonexistent path onto the array NEW_DIRS, allocating in
1272 POOL. Raise an error if the existing node is not a directory.
1274 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1275 ### implementation susceptible to race conditions. */
1276 static svn_error_t *
1277 find_absent_parents1(svn_ra_session_t *ra_session,
1279 apr_array_header_t *new_dirs,
1282 svn_node_kind_t kind;
1283 apr_pool_t *iterpool = svn_pool_create(pool);
1285 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1288 while (kind == svn_node_none)
1290 svn_pool_clear(iterpool);
1292 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1293 dir = svn_dirent_dirname(dir, pool);
1295 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1299 if (kind != svn_node_dir)
1300 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1301 _("Path '%s' already exists, but is not a "
1304 svn_pool_destroy(iterpool);
1305 return SVN_NO_ERROR;
1308 /* Starting with the URL *TOP_DST_URL which is also the root of
1309 RA_SESSION, work up through its parents until an existing node is
1310 found. Push each nonexistent URL onto the array NEW_DIRS,
1311 allocating in POOL. Raise an error if the existing node is not a
1314 Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1316 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1317 ### implementation susceptible to race conditions. */
1318 static svn_error_t *
1319 find_absent_parents2(svn_ra_session_t *ra_session,
1320 const char **top_dst_url,
1321 apr_array_header_t *new_dirs,
1324 const char *root_url = *top_dst_url;
1325 svn_node_kind_t kind;
1327 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1330 while (kind == svn_node_none)
1332 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1333 root_url = svn_uri_dirname(root_url, pool);
1335 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1336 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1340 if (kind != svn_node_dir)
1341 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1342 _("Path '%s' already exists, but is not a directory"),
1345 *top_dst_url = root_url;
1346 return SVN_NO_ERROR;
1349 /* Queue property changes for pinning svn:externals properties set on
1350 * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1351 * is keyed by the relative path of each descendant which should have some
1352 * or all of its externals pinned, with the corresponding pinned svn:externals
1353 * properties as values. Property changes are queued in a new list of path
1354 * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1355 * existing item is found for the descendant. Allocate results in RESULT_POOL.
1356 * Use SCRATCH_POOL for temporary allocations. */
1357 static svn_error_t *
1358 queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1359 apr_array_header_t *path_infos,
1360 apr_hash_t *pinned_externals,
1361 path_driver_info_t *parent_info,
1362 apr_pool_t *result_pool,
1363 apr_pool_t *scratch_pool)
1365 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1366 apr_hash_index_t *hi;
1368 for (hi = apr_hash_first(scratch_pool, pinned_externals);
1370 hi = apr_hash_next(hi))
1372 const char *dst_relpath = apr_hash_this_key(hi);
1373 svn_string_t *externals_prop = apr_hash_this_val(hi);
1374 const char *src_url;
1375 path_driver_info_t *info;
1378 svn_pool_clear(iterpool);
1380 src_url = svn_path_url_add_component2(parent_info->src_url,
1381 dst_relpath, iterpool);
1383 /* Try to find a path info the external change can be applied to. */
1385 for (i = 0; i < path_infos->nelts; i++)
1387 path_driver_info_t *existing_info;
1389 existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1390 if (strcmp(src_url, existing_info->src_url) == 0)
1392 info = existing_info;
1399 /* A copied-along child needs its externals pinned.
1400 Create a new path info for this property change. */
1401 info = apr_pcalloc(result_pool, sizeof(*info));
1402 info->src_url = svn_path_url_add_component2(
1403 parent_info->src_url, dst_relpath,
1405 info->src_path = NULL; /* Only needed on copied dirs */
1406 info->dst_path = svn_relpath_join(parent_info->dst_path,
1409 info->src_kind = svn_node_dir;
1410 info->only_pin_externals = TRUE;
1411 APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1414 info->externals = externals_prop;
1417 svn_pool_destroy(iterpool);
1419 return SVN_NO_ERROR;
1422 static svn_error_t *
1423 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
1424 svn_boolean_t make_parents,
1425 const apr_hash_t *revprop_table,
1426 svn_commit_callback2_t commit_callback,
1428 svn_client_ctx_t *ctx,
1429 svn_boolean_t is_move,
1430 svn_boolean_t pin_externals,
1431 const apr_hash_t *externals_to_pin,
1435 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1436 sizeof(const char *));
1437 apr_hash_t *action_hash = apr_hash_make(pool);
1438 apr_array_header_t *path_infos;
1439 const char *top_url, *top_url_all, *top_url_dst;
1440 const char *message, *repos_root;
1441 svn_ra_session_t *ra_session = NULL;
1442 const svn_delta_editor_t *editor;
1444 struct path_driver_cb_baton cb_baton;
1445 apr_array_header_t *new_dirs = NULL;
1446 apr_hash_t *commit_revprops;
1447 apr_array_header_t *pin_externals_only_infos = NULL;
1449 svn_client__copy_pair_t *first_pair =
1450 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1452 /* Open an RA session to the first copy pair's destination. We'll
1453 be verifying that every one of our copy source and destination
1454 URLs is or is beneath this sucker's repository root URL as a form
1455 of a cheap(ish) sanity check. */
1456 SVN_ERR(svn_client_open_ra_session2(&ra_session,
1457 first_pair->src_abspath_or_url, NULL,
1459 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1461 /* Verify that sources and destinations are all at or under
1462 REPOS_ROOT. While here, create a path_info struct for each
1463 src/dst pair and initialize portions of it with normalized source
1464 location information. */
1465 path_infos = apr_array_make(pool, copy_pairs->nelts,
1466 sizeof(path_driver_info_t *));
1467 for (i = 0; i < copy_pairs->nelts; i++)
1469 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1470 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1471 svn_client__copy_pair_t *);
1472 apr_hash_t *mergeinfo;
1474 /* Are the source and destination URLs at or under REPOS_ROOT? */
1475 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1476 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1477 return svn_error_create
1478 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1479 _("Source and destination URLs appear not to point to the "
1480 "same repository."));
1482 /* Run the history function to get the source's URL and revnum in the
1483 operational revision. */
1484 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1485 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1489 pair->src_abspath_or_url,
1490 &pair->src_peg_revision,
1491 &pair->src_op_revision, NULL,
1494 /* Go ahead and grab mergeinfo from the source, too. */
1495 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1496 SVN_ERR(svn_client__get_repos_mergeinfo(
1497 &mergeinfo, ra_session,
1498 pair->src_abspath_or_url, pair->src_revnum,
1499 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1501 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1503 /* Plop an INFO structure onto our array thereof. */
1504 info->src_url = pair->src_abspath_or_url;
1505 info->src_revnum = pair->src_revnum;
1506 info->resurrection = FALSE;
1507 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1510 /* If this is a move, we have to open our session to the longest
1511 path common to all SRC_URLS and DST_URLS in the repository so we
1512 can do existence checks on all paths, and so we can operate on
1513 all paths in the case of a move. But if this is *not* a move,
1514 then opening our session at the longest path common to sources
1515 *and* destinations might be an optimization when the user is
1516 authorized to access all that stuff, but could cause the
1517 operation to fail altogether otherwise. See issue #3242. */
1518 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1520 top_url = is_move ? top_url_all : top_url_dst;
1522 /* Check each src/dst pair for resurrection, and verify that TOP_URL
1523 is anchored high enough to cover all the editor_t activities
1524 required for this operation. */
1525 for (i = 0; i < copy_pairs->nelts; i++)
1527 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1528 svn_client__copy_pair_t *);
1529 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1530 path_driver_info_t *);
1532 /* Source and destination are the same? It's a resurrection. */
1533 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1534 info->resurrection = TRUE;
1536 /* We need to add each dst_URL, and (in a move) we'll need to
1537 delete each src_URL. Our selection of TOP_URL so far ensures
1538 that all our destination URLs (and source URLs, for moves)
1539 are at least as deep as TOP_URL, but we need to make sure
1540 that TOP_URL is an *ancestor* of all our to-be-edited paths.
1542 Issue #683 is demonstrates this scenario. If you're
1543 resurrecting a deleted item like this: 'svn cp -rN src_URL
1544 dst_URL', then src_URL == dst_URL == top_url. In this
1545 situation, we want to open an RA session to be at least the
1546 *parent* of all three. */
1547 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1548 && (strcmp(top_url, repos_root) != 0))
1550 top_url = svn_uri_dirname(top_url, pool);
1553 && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1554 && (strcmp(top_url, repos_root) != 0))
1556 top_url = svn_uri_dirname(top_url, pool);
1560 /* Point the RA session to our current TOP_URL. */
1561 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1563 /* If we're allowed to create nonexistent parent directories of our
1564 destinations, then make a list in NEW_DIRS of the parent
1565 directories of the destination that don't yet exist. */
1568 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1570 /* If this is a move, TOP_URL is at least the common ancestor of
1571 all the paths (sources and destinations) involved. Assuming
1572 the sources exist (which is fair, because if they don't, this
1573 whole operation will fail anyway), TOP_URL must also exist.
1574 So it's the paths between TOP_URL and the destinations which
1575 we have to check for existence. But here, we take advantage
1576 of the knowledge of our caller. We know that if there are
1577 multiple copy/move operations being requested, then the
1578 destinations of the copies/moves will all be siblings of one
1579 another. Therefore, we need only to check for the
1580 nonexistent paths between TOP_URL and *one* of our
1581 destinations to find nonexistent parents of all of them. */
1584 /* Imagine a situation where the user tries to copy an
1585 existing source directory to nonexistent directory with
1586 --parents options specified:
1588 svn copy --parents URL/src URL/dst
1590 where src exists and dst does not. If the dirname of the
1591 destination path is equal to TOP_URL,
1592 do not try to add dst to the NEW_DIRS list since it
1593 will be added to the commit items array later in this
1595 const char *dir = svn_uri_skip_ancestor(
1597 svn_uri_dirname(first_pair->dst_abspath_or_url,
1601 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1603 /* If, however, this is *not* a move, TOP_URL only points to the
1604 common ancestor of our destination path(s), or possibly one
1605 level higher. We'll need to do an existence crawl toward the
1606 root of the repository, starting with one of our destinations
1607 (see "... take advantage of the knowledge of our caller ..."
1608 above), and possibly adjusting TOP_URL as we go. */
1611 apr_array_header_t *new_urls =
1612 apr_array_make(pool, 0, sizeof(const char *));
1613 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1615 /* Convert absolute URLs into relpaths relative to TOP_URL. */
1616 for (i = 0; i < new_urls->nelts; i++)
1618 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1619 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1621 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1626 /* For each src/dst pair, check to see if that SRC_URL is a child of
1627 the DST_URL (excepting the case where DST_URL is the repo root).
1628 If it is, and the parent of DST_URL is the current TOP_URL, then we
1629 need to reparent the session one directory higher, the parent of
1631 for (i = 0; i < copy_pairs->nelts; i++)
1633 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1634 svn_client__copy_pair_t *);
1635 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1636 path_driver_info_t *);
1637 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1638 pair->src_abspath_or_url,
1641 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1642 && (relpath != NULL && *relpath != '\0'))
1644 info->resurrection = TRUE;
1645 top_url = svn_uri_get_longest_ancestor(
1647 svn_uri_dirname(pair->dst_abspath_or_url, pool),
1649 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1653 /* Get the portions of the SRC and DST URLs that are relative to
1654 TOP_URL (URI-decoding them while we're at it), verify that the
1655 source exists and the proposed destination does not, and toss
1656 what we've learned into the INFO array. (For copies -- that is,
1657 non-moves -- the relative source URL NULL because it isn't a
1658 child of the TOP_URL at all. That's okay, we'll deal with
1660 for (i = 0; i < copy_pairs->nelts; i++)
1662 svn_client__copy_pair_t *pair =
1663 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1664 path_driver_info_t *info =
1665 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1666 svn_node_kind_t dst_kind;
1667 const char *src_rel, *dst_rel;
1669 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1672 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1673 &info->src_kind, pool));
1677 const char *old_url;
1680 SVN_ERR_ASSERT(! is_move);
1682 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1683 pair->src_abspath_or_url,
1685 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1686 &info->src_kind, pool));
1687 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1689 if (info->src_kind == svn_node_none)
1690 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1691 _("Path '%s' does not exist in revision %ld"),
1692 pair->src_abspath_or_url, pair->src_revnum);
1694 /* Figure out the basename that will result from this operation,
1695 and ensure that we aren't trying to overwrite existing paths. */
1696 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1697 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1699 if (dst_kind != svn_node_none)
1701 const char *path = svn_uri_skip_ancestor(repos_root,
1702 pair->dst_abspath_or_url,
1704 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1705 _("Path '/%s' already exists"), path);
1708 /* More info for our INFO structure. */
1709 info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1710 info->dst_path = dst_rel;
1712 svn_hash_sets(action_hash, info->dst_path, info);
1713 if (is_move && (! info->resurrection))
1714 svn_hash_sets(action_hash, info->src_path, info);
1718 apr_hash_t *pinned_externals;
1720 SVN_ERR(resolve_pinned_externals(&pinned_externals,
1721 externals_to_pin, pair,
1722 ra_session, repos_root,
1724 if (pin_externals_only_infos == NULL)
1726 pin_externals_only_infos =
1727 apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1729 SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1736 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1738 /* Produce a list of new paths to add, and provide it to the
1739 mechanism used to acquire a log message. */
1740 svn_client_commit_item3_t *item;
1741 const char *tmp_file;
1742 apr_array_header_t *commit_items
1743 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1745 /* Add any intermediate directories to the message */
1748 for (i = 0; i < new_dirs->nelts; i++)
1750 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1752 item = svn_client_commit_item3_create(pool);
1753 item->url = svn_path_url_add_component2(top_url, relpath, pool);
1754 item->kind = svn_node_dir;
1755 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1756 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1760 for (i = 0; i < path_infos->nelts; i++)
1762 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1763 path_driver_info_t *);
1765 item = svn_client_commit_item3_create(pool);
1766 item->url = svn_path_url_add_component2(top_url, info->dst_path,
1768 item->kind = info->src_kind;
1769 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1770 | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1771 item->copyfrom_url = info->src_url;
1772 item->copyfrom_rev = info->src_revnum;
1773 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1775 if (is_move && (! info->resurrection))
1777 item = svn_client_commit_item3_create(pool);
1778 item->url = svn_path_url_add_component2(top_url, info->src_path,
1780 item->kind = info->src_kind;
1781 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1782 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1786 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1789 return SVN_NO_ERROR;
1794 /* Setup our PATHS for the path-based editor drive. */
1795 /* First any intermediate directories. */
1798 for (i = 0; i < new_dirs->nelts; i++)
1800 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1801 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1803 info->dst_path = relpath;
1804 info->dir_add = TRUE;
1806 APR_ARRAY_PUSH(paths, const char *) = relpath;
1807 svn_hash_sets(action_hash, relpath, info);
1811 /* Then our copy destinations and move sources (if any). */
1812 for (i = 0; i < path_infos->nelts; i++)
1814 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1815 path_driver_info_t *);
1817 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1818 if (is_move && (! info->resurrection))
1819 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1822 /* Add any items which only need their externals pinned. */
1823 if (pin_externals_only_infos)
1825 for (i = 0; i < pin_externals_only_infos->nelts; i++)
1827 path_driver_info_t *info;
1829 info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1830 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1831 svn_hash_sets(action_hash, info->dst_path, info);
1835 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1836 message, ctx, pool));
1838 /* Fetch RA commit editor. */
1839 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1840 svn_client__get_shim_callbacks(ctx->wc_ctx,
1842 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1846 NULL, TRUE, /* No lock tokens */
1849 /* Setup the callback baton. */
1850 cb_baton.editor = editor;
1851 cb_baton.edit_baton = edit_baton;
1852 cb_baton.action_hash = action_hash;
1853 cb_baton.is_move = is_move;
1855 /* Call the path-based editor driver. */
1856 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1857 path_driver_cb_func, &cb_baton, pool);
1860 /* At least try to abort the edit (and fs txn) before throwing err. */
1861 return svn_error_compose_create(
1863 editor->abort_edit(edit_baton, pool));
1866 if (ctx->notify_func2)
1868 svn_wc_notify_t *notify;
1869 notify = svn_wc_create_notify_url(top_url,
1870 svn_wc_notify_commit_finalizing,
1872 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1875 /* Close the edit. */
1876 return svn_error_trace(editor->close_edit(edit_baton, pool));
1879 /* Baton for check_url_kind */
1880 struct check_url_kind_baton
1882 svn_ra_session_t *session;
1883 const char *repos_root_url;
1884 svn_boolean_t should_reparent;
1887 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1888 static svn_error_t *
1889 check_url_kind(void *baton,
1890 svn_node_kind_t *kind,
1892 svn_revnum_t revision,
1893 apr_pool_t *scratch_pool)
1895 struct check_url_kind_baton *cukb = baton;
1897 /* If we don't have a session or can't use the session, get one */
1898 if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1899 *kind = svn_node_none;
1902 cukb->should_reparent = TRUE;
1904 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1906 SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1907 kind, scratch_pool));
1910 return SVN_NO_ERROR;
1913 /* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1914 * in the COMMIT_ITEMS list.
1915 * If the list does not already have a commit item for COMMIT_URL
1916 * add a new commit item for the property change.
1917 * Allocate results in RESULT_POOL.
1918 * Use SCRATCH_POOL for temporary allocations. */
1919 static svn_error_t *
1920 queue_prop_change_commit_items(const char *local_abspath,
1921 const char *commit_url,
1922 apr_array_header_t *commit_items,
1923 const char *propname,
1924 svn_string_t *propval,
1925 apr_pool_t *result_pool,
1926 apr_pool_t *scratch_pool)
1928 svn_client_commit_item3_t *item = NULL;
1932 for (i = 0; i < commit_items->nelts; i++)
1934 svn_client_commit_item3_t *existing_item;
1936 existing_item = APR_ARRAY_IDX(commit_items, i,
1937 svn_client_commit_item3_t *);
1938 if (strcmp(existing_item->url, commit_url) == 0)
1940 item = existing_item;
1947 item = svn_client_commit_item3_create(result_pool);
1948 item->path = local_abspath;
1949 item->url = commit_url;
1950 item->kind = svn_node_dir;
1951 item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1953 item->incoming_prop_changes = apr_array_make(result_pool, 1,
1954 sizeof(svn_prop_t *));
1955 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1958 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1960 if (item->outgoing_prop_changes == NULL)
1961 item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1962 sizeof(svn_prop_t *));
1964 prop = apr_palloc(result_pool, sizeof(*prop));
1965 prop->name = propname;
1966 prop->value = propval;
1967 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1969 return SVN_NO_ERROR;
1973 * COMMIT_INFO_P is ...
1974 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1975 * and each 'dst_abspath_or_url' is a URL.
1976 * MAKE_PARENTS is ...
1977 * REVPROP_TABLE is ...
1979 static svn_error_t *
1980 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1981 svn_boolean_t make_parents,
1982 const apr_hash_t *revprop_table,
1983 svn_commit_callback2_t commit_callback,
1985 svn_boolean_t pin_externals,
1986 const apr_hash_t *externals_to_pin,
1987 svn_client_ctx_t *ctx,
1988 apr_pool_t *scratch_pool)
1990 const char *message;
1991 const char *top_src_path, *top_dst_url;
1992 struct check_url_kind_baton cukb;
1993 const char *top_src_abspath;
1994 svn_ra_session_t *ra_session;
1995 const svn_delta_editor_t *editor;
1996 #ifdef ENABLE_EV2_SHIMS
1997 apr_hash_t *relpath_map = NULL;
2000 svn_client__committables_t *committables;
2001 apr_array_header_t *commit_items;
2002 apr_pool_t *iterpool;
2003 apr_array_header_t *new_dirs = NULL;
2004 apr_hash_t *commit_revprops;
2005 svn_client__copy_pair_t *first_pair;
2006 apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2007 apr_array_header_t *commit_items_for_dav;
2010 /* Find the common root of all the source paths */
2011 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2014 /* Do we need to lock the working copy? 1.6 didn't take a write
2015 lock, but what happens if the working copy changes during the copy
2018 iterpool = svn_pool_create(scratch_pool);
2020 /* Determine the longest common ancestor for the destinations, and open an RA
2021 session to that location. */
2022 /* ### But why start by getting the _parent_ of the first one? */
2023 /* --- That works because multiple destinations always point to the same
2024 * directory. I'm rather wondering why we need to find a common
2025 * destination parent here at all, instead of simply getting
2026 * top_dst_url from get_copy_pair_ancestors() above?
2027 * It looks like the entire block of code hanging off this comment
2029 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2030 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2031 for (i = 1; i < copy_pairs->nelts; i++)
2033 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2034 svn_client__copy_pair_t *);
2035 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2036 pair->dst_abspath_or_url,
2040 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2042 commit_items_for_dav = apr_array_make(session_pool, 0,
2043 sizeof(svn_client_commit_item3_t*));
2045 /* Open a session to help while determining the exact targets */
2046 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2048 commit_items_for_dav,
2049 FALSE /* write_dav_props */,
2050 TRUE /* read_dav_props */,
2052 session_pool, session_pool));
2054 /* If requested, determine the nearest existing parent of the destination,
2055 and reparent the ra session there. */
2058 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2059 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2063 /* Figure out the basename that will result from each copy and check to make
2064 sure it doesn't exist already. */
2065 for (i = 0; i < copy_pairs->nelts; i++)
2067 svn_node_kind_t dst_kind;
2068 const char *dst_rel;
2069 svn_client__copy_pair_t *pair =
2070 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2072 svn_pool_clear(iterpool);
2073 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2075 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2076 &dst_kind, iterpool));
2077 if (dst_kind != svn_node_none)
2079 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2080 _("Path '%s' already exists"),
2081 pair->dst_abspath_or_url);
2085 cukb.session = ra_session;
2086 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2087 cukb.should_reparent = FALSE;
2089 /* Crawl the working copy for commit items. */
2090 /* ### TODO: Pass check_url_func for issue #3314 handling */
2091 SVN_ERR(svn_client__get_copy_committables(&committables,
2093 check_url_kind, &cukb,
2094 ctx, scratch_pool, iterpool));
2096 /* The committables are keyed by the repository root */
2097 commit_items = svn_hash_gets(committables->by_repository,
2098 cukb.repos_root_url);
2099 SVN_ERR_ASSERT(commit_items != NULL);
2101 if (cukb.should_reparent)
2102 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2104 /* If we are creating intermediate directories, tack them onto the list
2108 for (i = 0; i < new_dirs->nelts; i++)
2110 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2111 svn_client_commit_item3_t *item;
2113 item = svn_client_commit_item3_create(scratch_pool);
2115 item->kind = svn_node_dir;
2116 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2117 item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2118 sizeof(svn_prop_t *));
2119 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2123 /* ### TODO: This extra loop would be unnecessary if this code lived
2124 ### in svn_client__get_copy_committables(), which is incidentally
2125 ### only used above (so should really be in this source file). */
2126 for (i = 0; i < copy_pairs->nelts; i++)
2128 apr_hash_t *mergeinfo, *wc_mergeinfo;
2129 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2130 svn_client__copy_pair_t *);
2131 svn_client_commit_item3_t *item =
2132 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2133 svn_client__pathrev_t *src_origin;
2135 svn_pool_clear(iterpool);
2137 SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2138 pair->src_abspath_or_url,
2139 ctx, iterpool, iterpool));
2141 /* Set the mergeinfo for the destination to the combined merge
2142 info known to the WC and the repository. */
2143 /* Repository mergeinfo (or NULL if it's locally added)... */
2145 SVN_ERR(svn_client__get_repos_mergeinfo(
2146 &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2147 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2150 /* ... and WC mergeinfo. */
2151 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2152 pair->src_abspath_or_url,
2153 iterpool, iterpool));
2154 if (wc_mergeinfo && mergeinfo)
2155 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2157 else if (! mergeinfo)
2158 mergeinfo = wc_mergeinfo;
2162 /* Push a mergeinfo prop representing MERGEINFO onto the
2163 * OUTGOING_PROP_CHANGES array. */
2165 svn_prop_t *mergeinfo_prop
2166 = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2167 svn_string_t *prop_value;
2169 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2172 if (!item->outgoing_prop_changes)
2174 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2175 sizeof(svn_prop_t *));
2178 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2179 mergeinfo_prop->value = prop_value;
2180 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2186 apr_hash_t *pinned_externals;
2187 apr_hash_index_t *hi;
2189 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2190 externals_to_pin, pair,
2191 ra_session, cukb.repos_root_url,
2192 ctx, scratch_pool, iterpool));
2193 for (hi = apr_hash_first(scratch_pool, pinned_externals);
2195 hi = apr_hash_next(hi))
2197 const char *dst_relpath = apr_hash_this_key(hi);
2198 svn_string_t *externals_propval = apr_hash_this_val(hi);
2199 const char *dst_url;
2200 const char *commit_url;
2201 const char *src_abspath;
2203 if (svn_path_is_url(pair->dst_abspath_or_url))
2204 dst_url = pair->dst_abspath_or_url;
2206 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2207 pair->dst_abspath_or_url,
2208 scratch_pool, iterpool));
2209 commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2211 src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2212 dst_relpath, iterpool);
2213 SVN_ERR(queue_prop_change_commit_items(src_abspath,
2214 commit_url, commit_items,
2217 scratch_pool, iterpool));
2222 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2224 const char *tmp_file;
2226 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2227 ctx, scratch_pool));
2230 svn_pool_destroy(iterpool);
2231 svn_pool_destroy(session_pool);
2232 return SVN_NO_ERROR;
2238 /* Sort and condense our COMMIT_ITEMS. */
2239 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2240 commit_items, scratch_pool));
2242 /* Add the commit items to the DAV commit item list to provide access
2243 to dav properties (for pre http-v2 DAV) */
2244 apr_array_cat(commit_items_for_dav, commit_items);
2246 #ifdef ENABLE_EV2_SHIMS
2249 relpath_map = apr_hash_make(scratch_pool);
2250 for (i = 0; i < commit_items->nelts; i++)
2252 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2253 svn_client_commit_item3_t *);
2254 const char *relpath;
2259 svn_pool_clear(iterpool);
2260 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2262 ctx->wc_ctx, item->path, FALSE,
2263 scratch_pool, iterpool));
2265 svn_hash_sets(relpath_map, relpath, item->path);
2270 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2272 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2273 message, ctx, session_pool));
2275 /* Fetch RA commit editor. */
2276 #ifdef ENABLE_EV2_SHIMS
2277 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2278 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2281 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2285 TRUE, /* No lock tokens */
2288 /* Perform the commit. */
2289 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2291 NULL /* notify_path_prefix */,
2292 NULL, ctx, session_pool, session_pool),
2293 _("Commit failed (details follow):"));
2295 svn_pool_destroy(iterpool);
2296 svn_pool_destroy(session_pool);
2298 return SVN_NO_ERROR;
2301 /* A baton for notification_adjust_func(). */
2302 struct notification_adjust_baton
2304 svn_wc_notify_func2_t inner_func;
2306 const char *checkout_abspath;
2307 const char *final_abspath;
2310 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2311 * baton is BATON->inner_baton) and adjusts the notification paths that
2312 * start with BATON->checkout_abspath to start instead with
2313 * BATON->final_abspath. */
2315 notification_adjust_func(void *baton,
2316 const svn_wc_notify_t *notify,
2319 struct notification_adjust_baton *nb = baton;
2320 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2321 const char *relpath;
2323 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2324 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2327 nb->inner_func(nb->inner_baton, inner_notify, pool);
2330 /* Peform each individual copy operation for a repos -> wc copy. A
2331 helper for repos_to_wc_copy().
2333 Resolve PAIR->src_revnum to a real revision number if it isn't already. */
2334 static svn_error_t *
2335 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2336 svn_client__copy_pair_t *pair,
2337 svn_boolean_t same_repositories,
2338 svn_boolean_t ignore_externals,
2339 svn_boolean_t pin_externals,
2340 const apr_hash_t *externals_to_pin,
2341 svn_ra_session_t *ra_session,
2342 svn_client_ctx_t *ctx,
2345 apr_hash_t *src_mergeinfo;
2346 const char *dst_abspath = pair->dst_abspath_or_url;
2348 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2350 if (!same_repositories && ctx->notify_func2)
2352 svn_wc_notify_t *notify;
2353 notify = svn_wc_create_notify_url(
2354 pair->src_abspath_or_url,
2355 svn_wc_notify_foreign_copy_begin,
2357 notify->kind = pair->src_kind;
2358 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2360 /* Allow a theoretical cancel to get through. */
2361 if (ctx->cancel_func)
2362 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2365 if (pair->src_kind == svn_node_dir)
2367 if (same_repositories)
2369 const char *tmpdir_abspath, *tmp_abspath;
2371 /* Find a temporary location in which to check out the copy source. */
2372 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2375 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2376 svn_io_file_del_on_close, pool, pool));
2378 /* Make a new checkout of the requested source. While doing so,
2379 * resolve pair->src_revnum to an actual revision number in case it
2380 * was until now 'invalid' meaning 'head'. Ask this function not to
2381 * sleep for timestamps, by passing a sleep_needed output param.
2382 * Send notifications for all nodes except the root node, and adjust
2383 * them to refer to the destination rather than this temporary path. */
2385 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2386 void *old_notify_baton2 = ctx->notify_baton2;
2387 struct notification_adjust_baton nb;
2390 nb.inner_func = ctx->notify_func2;
2391 nb.inner_baton = ctx->notify_baton2;
2392 nb.checkout_abspath = tmp_abspath;
2393 nb.final_abspath = dst_abspath;
2394 ctx->notify_func2 = notification_adjust_func;
2395 ctx->notify_baton2 = &nb;
2397 /* Avoid a chicken-and-egg problem:
2398 * If pinning externals we'll need to adjust externals
2399 * properties before checking out any externals.
2400 * But copy needs to happen before pinning because else there
2401 * are no svn:externals properties to pin. */
2403 ignore_externals = TRUE;
2405 err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
2408 &pair->src_peg_revision,
2409 &pair->src_op_revision,
2411 ignore_externals, FALSE,
2412 ra_session, ctx, pool);
2414 ctx->notify_func2 = old_notify_func2;
2415 ctx->notify_baton2 = old_notify_baton2;
2420 *timestamp_sleep = TRUE;
2422 /* Schedule dst_path for addition in parent, with copy history.
2423 Don't send any notification here.
2424 Then remove the temporary checkout's .svn dir in preparation for
2425 moving the rest of it into the final destination. */
2426 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2427 TRUE /* metadata_only */,
2428 ctx->cancel_func, ctx->cancel_baton,
2430 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2431 FALSE, pool, pool));
2432 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2439 /* Move the temporary disk tree into place. */
2440 SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
2444 *timestamp_sleep = TRUE;
2446 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
2448 &pair->src_peg_revision,
2449 &pair->src_op_revision,
2451 FALSE /* make_parents */,
2452 TRUE /* already_locked */,
2455 return SVN_NO_ERROR;
2460 apr_hash_t *pinned_externals;
2461 apr_hash_index_t *hi;
2462 apr_pool_t *iterpool;
2463 const char *repos_root_url;
2464 apr_hash_t *new_externals;
2465 apr_hash_t *new_depths;
2467 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2468 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2469 externals_to_pin, pair,
2470 ra_session, repos_root_url,
2473 iterpool = svn_pool_create(pool);
2474 for (hi = apr_hash_first(pool, pinned_externals);
2476 hi = apr_hash_next(hi))
2478 const char *dst_relpath = apr_hash_this_key(hi);
2479 svn_string_t *externals_propval = apr_hash_this_val(hi);
2480 const char *local_abspath;
2482 svn_pool_clear(iterpool);
2484 local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2485 dst_relpath, iterpool);
2486 /* ### use a work queue? */
2487 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2488 SVN_PROP_EXTERNALS, externals_propval,
2489 svn_depth_empty, TRUE /* skip_checks */,
2490 NULL /* changelist_filter */,
2491 ctx->cancel_func, ctx->cancel_baton,
2492 NULL, NULL, /* no extra notification */
2496 /* Now update all externals in the newly created copy. */
2497 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2502 iterpool, iterpool));
2503 SVN_ERR(svn_client__handle_externals(new_externals,
2505 repos_root_url, dst_abspath,
2510 svn_pool_destroy(iterpool);
2512 } /* end directory case */
2514 else if (pair->src_kind == svn_node_file)
2516 apr_hash_t *new_props;
2517 const char *src_rel;
2518 svn_stream_t *new_base_contents = svn_stream_buffered(pool);
2520 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2521 pair->src_abspath_or_url,
2523 /* Fetch the file content. While doing so, resolve pair->src_revnum
2524 * to an actual revision number if it's 'invalid' meaning 'head'. */
2525 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
2527 &pair->src_revnum, &new_props, pool));
2529 if (new_props && ! same_repositories)
2530 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2532 *timestamp_sleep = TRUE;
2534 SVN_ERR(svn_wc_add_repos_file4(
2535 ctx->wc_ctx, dst_abspath,
2536 new_base_contents, NULL, new_props, NULL,
2537 same_repositories ? pair->src_abspath_or_url : NULL,
2538 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
2539 ctx->cancel_func, ctx->cancel_baton,
2543 /* Record the implied mergeinfo (before the notification callback
2544 is invoked for the root node). */
2545 SVN_ERR(svn_client__get_repos_mergeinfo(
2546 &src_mergeinfo, ra_session,
2547 pair->src_abspath_or_url, pair->src_revnum,
2548 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
2549 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2551 /* Do our own notification for the root node, even if we could possibly
2552 have delegated it. See also issue #1552.
2554 ### Maybe this notification should mention the mergeinfo change. */
2555 if (ctx->notify_func2)
2557 svn_wc_notify_t *notify = svn_wc_create_notify(
2558 dst_abspath, svn_wc_notify_add, pool);
2559 notify->kind = pair->src_kind;
2560 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2563 return SVN_NO_ERROR;
2566 static svn_error_t *
2567 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2568 const apr_array_header_t *copy_pairs,
2569 const char *top_dst_path,
2570 svn_boolean_t ignore_externals,
2571 svn_boolean_t pin_externals,
2572 const apr_hash_t *externals_to_pin,
2573 svn_ra_session_t *ra_session,
2574 svn_client_ctx_t *ctx,
2575 apr_pool_t *scratch_pool)
2578 svn_boolean_t same_repositories;
2579 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2581 /* We've already checked for physical obstruction by a working file.
2582 But there could also be logical obstruction by an entry whose
2583 working file happens to be missing.*/
2584 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
2585 ctx, scratch_pool, iterpool));
2587 /* Decide whether the two repositories are the same or not. */
2589 svn_error_t *src_err, *dst_err;
2591 const char *parent_abspath;
2592 const char *src_uuid, *dst_uuid;
2594 /* Get the repository uuid of SRC_URL */
2595 src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
2596 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
2597 return svn_error_trace(src_err);
2599 /* Get repository uuid of dst's parent directory, since dst may
2600 not exist. ### TODO: we should probably walk up the wc here,
2601 in case the parent dir has an imaginary URL. */
2602 if (copy_pairs->nelts == 1)
2603 parent = svn_dirent_dirname(top_dst_path, scratch_pool);
2605 parent = top_dst_path;
2607 SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
2608 dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
2609 parent_abspath, ctx,
2610 iterpool, iterpool);
2611 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
2614 /* If either of the UUIDs are nonexistent, then at least one of
2615 the repositories must be very old. Rather than punish the
2616 user, just assume the repositories are different, so no
2617 copy-history is attempted. */
2618 if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
2619 same_repositories = FALSE;
2621 same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
2624 /* Perform the move for each of the copy_pairs. */
2625 for (i = 0; i < copy_pairs->nelts; i++)
2627 /* Check for cancellation */
2628 if (ctx->cancel_func)
2629 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2631 svn_pool_clear(iterpool);
2633 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2634 APR_ARRAY_IDX(copy_pairs, i,
2635 svn_client__copy_pair_t *),
2638 pin_externals, externals_to_pin,
2639 ra_session, ctx, iterpool));
2641 svn_pool_destroy(iterpool);
2643 return SVN_NO_ERROR;
2646 static svn_error_t *
2647 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2648 const apr_array_header_t *copy_pairs,
2649 svn_boolean_t make_parents,
2650 svn_boolean_t ignore_externals,
2651 svn_boolean_t pin_externals,
2652 const apr_hash_t *externals_to_pin,
2653 svn_client_ctx_t *ctx,
2656 svn_ra_session_t *ra_session;
2657 const char *top_src_url, *top_dst_path;
2658 apr_pool_t *iterpool = svn_pool_create(pool);
2659 const char *lock_abspath;
2662 /* Get the real path for the source, based upon its peg revision. */
2663 for (i = 0; i < copy_pairs->nelts; i++)
2665 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2666 svn_client__copy_pair_t *);
2669 svn_pool_clear(iterpool);
2671 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2673 pair->src_abspath_or_url,
2674 &pair->src_peg_revision,
2675 &pair->src_op_revision, NULL,
2678 pair->src_original = pair->src_abspath_or_url;
2679 pair->src_abspath_or_url = apr_pstrdup(pool, src);
2682 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
2684 lock_abspath = top_dst_path;
2685 if (copy_pairs->nelts == 1)
2687 top_src_url = svn_uri_dirname(top_src_url, pool);
2688 lock_abspath = svn_dirent_dirname(top_dst_path, pool);
2691 /* Open a repository session to the longest common src ancestor. We do not
2692 (yet) have a working copy, so we don't have a corresponding path and
2693 tempfiles cannot go into the admin area. */
2694 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2697 /* Get the correct src path for the peg revision used, and verify that we
2698 aren't overwriting an existing path. */
2699 for (i = 0; i < copy_pairs->nelts; i++)
2701 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2702 svn_client__copy_pair_t *);
2703 svn_node_kind_t dst_parent_kind, dst_kind;
2704 const char *dst_parent;
2705 const char *src_rel;
2707 svn_pool_clear(iterpool);
2709 /* Next, make sure that the path exists in the repository. */
2710 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2711 pair->src_abspath_or_url,
2713 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2714 &pair->src_kind, pool));
2715 if (pair->src_kind == svn_node_none)
2717 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2718 return svn_error_createf
2719 (SVN_ERR_FS_NOT_FOUND, NULL,
2720 _("Path '%s' not found in revision %ld"),
2721 pair->src_abspath_or_url, pair->src_revnum);
2723 return svn_error_createf
2724 (SVN_ERR_FS_NOT_FOUND, NULL,
2725 _("Path '%s' not found in head revision"),
2726 pair->src_abspath_or_url);
2729 /* Figure out about dst. */
2730 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
2732 if (dst_kind != svn_node_none)
2734 return svn_error_createf(
2735 SVN_ERR_ENTRY_EXISTS, NULL,
2736 _("Path '%s' already exists"),
2737 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2740 /* Make sure the destination parent is a directory and produce a clear
2741 error message if it is not. */
2742 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
2743 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
2744 if (make_parents && dst_parent_kind == svn_node_none)
2746 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
2749 else if (dst_parent_kind != svn_node_dir)
2751 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
2752 _("Path '%s' is not a directory"),
2753 svn_dirent_local_style(dst_parent, pool));
2756 svn_pool_destroy(iterpool);
2758 SVN_WC__CALL_WITH_WRITE_LOCK(
2759 repos_to_wc_copy_locked(timestamp_sleep,
2760 copy_pairs, top_dst_path, ignore_externals,
2761 pin_externals, externals_to_pin,
2762 ra_session, ctx, pool),
2763 ctx->wc_ctx, lock_abspath, FALSE, pool);
2764 return SVN_NO_ERROR;
2767 #define NEED_REPOS_REVNUM(revision) \
2768 ((revision.kind != svn_opt_revision_unspecified) \
2769 && (revision.kind != svn_opt_revision_working))
2773 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2774 * change *TIMESTAMP_SLEEP. This output will be valid even if the
2775 * function returns an error.
2777 * Perform all allocations in POOL.
2779 static svn_error_t *
2780 try_copy(svn_boolean_t *timestamp_sleep,
2781 const apr_array_header_t *sources,
2782 const char *dst_path_in,
2783 svn_boolean_t is_move,
2784 svn_boolean_t allow_mixed_revisions,
2785 svn_boolean_t metadata_only,
2786 svn_boolean_t make_parents,
2787 svn_boolean_t ignore_externals,
2788 svn_boolean_t pin_externals,
2789 const apr_hash_t *externals_to_pin,
2790 const apr_hash_t *revprop_table,
2791 svn_commit_callback2_t commit_callback,
2793 svn_client_ctx_t *ctx,
2796 apr_array_header_t *copy_pairs =
2797 apr_array_make(pool, sources->nelts,
2798 sizeof(svn_client__copy_pair_t *));
2799 svn_boolean_t srcs_are_urls, dst_is_url;
2802 /* Assert instead of crashing if the sources list is empty. */
2803 SVN_ERR_ASSERT(sources->nelts > 0);
2805 /* Are either of our paths URLs? Just check the first src_path. If
2806 there are more than one, we'll check for homogeneity among them
2808 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2809 svn_client_copy_source_t *)->path);
2810 dst_is_url = svn_path_is_url(dst_path_in);
2812 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2814 /* If we have multiple source paths, it implies the dst_path is a
2815 directory we are moving or copying into. Populate the COPY_PAIRS
2816 array to contain a destination path for each of the source paths. */
2817 if (sources->nelts > 1)
2819 apr_pool_t *iterpool = svn_pool_create(pool);
2821 for (i = 0; i < sources->nelts; i++)
2823 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
2824 svn_client_copy_source_t *);
2825 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2826 const char *src_basename;
2827 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2829 svn_pool_clear(iterpool);
2833 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2834 src_basename = svn_uri_basename(pair->src_abspath_or_url,
2839 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2840 source->path, pool));
2841 src_basename = svn_dirent_basename(pair->src_abspath_or_url,
2845 pair->src_op_revision = *source->revision;
2846 pair->src_peg_revision = *source->peg_revision;
2847 pair->src_kind = svn_node_unknown;
2849 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2850 &pair->src_op_revision,
2855 /* Check to see if all the sources are urls or all working copy
2857 if (src_is_url != srcs_are_urls)
2858 return svn_error_create
2859 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2860 _("Cannot mix repository and working copy sources"));
2863 pair->dst_abspath_or_url =
2864 svn_path_url_add_component2(dst_path_in, src_basename, pool);
2866 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2867 src_basename, pool);
2868 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2871 svn_pool_destroy(iterpool);
2875 /* Only one source path. */
2876 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2877 svn_client_copy_source_t *source =
2878 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2879 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2882 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2884 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2885 source->path, pool));
2886 pair->src_op_revision = *source->revision;
2887 pair->src_peg_revision = *source->peg_revision;
2888 pair->src_kind = svn_node_unknown;
2890 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2891 &pair->src_op_revision,
2892 src_is_url, TRUE, pool));
2894 pair->dst_abspath_or_url = dst_path_in;
2895 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2898 if (!srcs_are_urls && !dst_is_url)
2900 apr_pool_t *iterpool = svn_pool_create(pool);
2902 for (i = 0; i < copy_pairs->nelts; i++)
2904 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2905 svn_client__copy_pair_t *);
2907 svn_pool_clear(iterpool);
2909 if (svn_dirent_is_child(pair->src_abspath_or_url,
2910 pair->dst_abspath_or_url, iterpool))
2911 return svn_error_createf
2912 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2913 _("Cannot copy path '%s' into its own child '%s'"),
2914 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2915 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2918 svn_pool_destroy(iterpool);
2921 /* A file external should not be moved since the file external is
2922 implemented as a switched file and it would delete the file the
2923 file external is switched to, which is not the behavior the user
2924 would probably want. */
2925 if (is_move && !srcs_are_urls)
2927 apr_pool_t *iterpool = svn_pool_create(pool);
2929 for (i = 0; i < copy_pairs->nelts; i++)
2931 svn_client__copy_pair_t *pair =
2932 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2933 svn_node_kind_t external_kind;
2934 const char *defining_abspath;
2936 svn_pool_clear(iterpool);
2938 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2939 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2940 NULL, NULL, NULL, ctx->wc_ctx,
2941 pair->src_abspath_or_url,
2942 pair->src_abspath_or_url, TRUE,
2943 iterpool, iterpool));
2945 if (external_kind != svn_node_none)
2946 return svn_error_createf(
2947 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2949 _("Cannot move the external at '%s'; please "
2950 "edit the svn:externals property on '%s'."),
2951 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2952 svn_dirent_local_style(defining_abspath, pool));
2954 svn_pool_destroy(iterpool);
2959 /* Disallow moves between the working copy and the repository. */
2960 if (srcs_are_urls != dst_is_url)
2962 return svn_error_create
2963 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2964 _("Moves between the working copy and the repository are not "
2968 /* Disallow moving any path/URL onto or into itself. */
2969 for (i = 0; i < copy_pairs->nelts; i++)
2971 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2972 svn_client__copy_pair_t *);
2974 if (strcmp(pair->src_abspath_or_url,
2975 pair->dst_abspath_or_url) == 0)
2976 return svn_error_createf(
2977 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2979 _("Cannot move URL '%s' into itself") :
2980 _("Cannot move path '%s' into itself"),
2982 pair->src_abspath_or_url :
2983 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2990 /* If we are doing a wc->* copy, but with an operational revision
2991 other than the working copy revision, we are really doing a
2992 repo->* copy, because we're going to need to get the rev from the
2995 svn_boolean_t need_repos_op_rev = FALSE;
2996 svn_boolean_t need_repos_peg_rev = FALSE;
2998 /* Check to see if any revision is something other than
2999 svn_opt_revision_unspecified or svn_opt_revision_working. */
3000 for (i = 0; i < copy_pairs->nelts; i++)
3002 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3003 svn_client__copy_pair_t *);
3005 if (NEED_REPOS_REVNUM(pair->src_op_revision))
3006 need_repos_op_rev = TRUE;
3008 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3009 need_repos_peg_rev = TRUE;
3011 if (need_repos_op_rev || need_repos_peg_rev)
3015 if (need_repos_op_rev || need_repos_peg_rev)
3017 apr_pool_t *iterpool = svn_pool_create(pool);
3019 for (i = 0; i < copy_pairs->nelts; i++)
3021 const char *copyfrom_repos_root_url;
3022 const char *copyfrom_repos_relpath;
3024 svn_revnum_t copyfrom_rev;
3025 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3026 svn_client__copy_pair_t *);
3028 svn_pool_clear(iterpool);
3030 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3032 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev,
3033 ©from_repos_relpath,
3034 ©from_repos_root_url,
3037 pair->src_abspath_or_url,
3038 TRUE, iterpool, iterpool));
3040 if (copyfrom_repos_relpath)
3041 url = svn_path_url_add_component2(copyfrom_repos_root_url,
3042 copyfrom_repos_relpath,
3045 return svn_error_createf
3046 (SVN_ERR_ENTRY_MISSING_URL, NULL,
3047 _("'%s' does not have a URL associated with it"),
3048 svn_dirent_local_style(pair->src_abspath_or_url, pool));
3050 pair->src_abspath_or_url = url;
3052 if (!need_repos_peg_rev
3053 || pair->src_peg_revision.kind == svn_opt_revision_base)
3055 /* Default the peg revision to that of the WC entry. */
3056 pair->src_peg_revision.kind = svn_opt_revision_number;
3057 pair->src_peg_revision.value.number = copyfrom_rev;
3060 if (pair->src_op_revision.kind == svn_opt_revision_base)
3062 /* Use the entry's revision as the operational rev. */
3063 pair->src_op_revision.kind = svn_opt_revision_number;
3064 pair->src_op_revision.value.number = copyfrom_rev;
3068 svn_pool_destroy(iterpool);
3069 srcs_are_urls = TRUE;
3074 /* Now, call the right handler for the operation. */
3075 if ((! srcs_are_urls) && (! dst_is_url))
3077 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
3078 metadata_only, ctx, pool, pool));
3080 /* Copy or move all targets. */
3082 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3083 copy_pairs, dst_path_in,
3084 allow_mixed_revisions,
3089 /* We ignore these values, so assert the default value */
3090 SVN_ERR_ASSERT(allow_mixed_revisions);
3091 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3099 else if ((! srcs_are_urls) && (dst_is_url))
3101 return svn_error_trace(
3102 wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3103 commit_callback, commit_baton,
3104 pin_externals, externals_to_pin, ctx, pool));
3106 else if ((srcs_are_urls) && (! dst_is_url))
3108 return svn_error_trace(
3109 repos_to_wc_copy(timestamp_sleep,
3110 copy_pairs, make_parents, ignore_externals,
3111 pin_externals, externals_to_pin, ctx, pool));
3115 return svn_error_trace(
3116 repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3117 commit_callback, commit_baton, ctx, is_move,
3118 pin_externals, externals_to_pin, pool));
3124 /* Public Interfaces */
3126 svn_client_copy7(const apr_array_header_t *sources,
3127 const char *dst_path,
3128 svn_boolean_t copy_as_child,
3129 svn_boolean_t make_parents,
3130 svn_boolean_t ignore_externals,
3131 svn_boolean_t metadata_only,
3132 svn_boolean_t pin_externals,
3133 const apr_hash_t *externals_to_pin,
3134 const apr_hash_t *revprop_table,
3135 svn_commit_callback2_t commit_callback,
3137 svn_client_ctx_t *ctx,
3141 svn_boolean_t timestamp_sleep = FALSE;
3142 apr_pool_t *subpool = svn_pool_create(pool);
3144 if (sources->nelts > 1 && !copy_as_child)
3145 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3148 err = try_copy(×tamp_sleep,
3150 FALSE /* is_move */,
3151 TRUE /* allow_mixed_revisions */,
3158 commit_callback, commit_baton,
3162 /* If the destination exists, try to copy the sources as children of the
3164 if (copy_as_child && err && (sources->nelts == 1)
3165 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3166 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3168 const char *src_path = APR_ARRAY_IDX(sources, 0,
3169 svn_client_copy_source_t *)->path;
3170 const char *src_basename;
3171 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3172 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3174 svn_error_clear(err);
3175 svn_pool_clear(subpool);
3177 src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3178 : svn_dirent_basename(src_path, subpool);
3180 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3182 : svn_dirent_join(dst_path, src_basename, subpool);
3184 err = try_copy(×tamp_sleep,
3186 FALSE /* is_move */,
3187 TRUE /* allow_mixed_revisions */,
3194 commit_callback, commit_baton,
3199 /* Sleep if required. DST_PATH is not a URL in these cases. */
3200 if (timestamp_sleep)
3201 svn_io_sleep_for_timestamps(dst_path, subpool);
3203 svn_pool_destroy(subpool);
3204 return svn_error_trace(err);
3209 svn_client_move7(const apr_array_header_t *src_paths,
3210 const char *dst_path,
3211 svn_boolean_t move_as_child,
3212 svn_boolean_t make_parents,
3213 svn_boolean_t allow_mixed_revisions,
3214 svn_boolean_t metadata_only,
3215 const apr_hash_t *revprop_table,
3216 svn_commit_callback2_t commit_callback,
3218 svn_client_ctx_t *ctx,
3221 const svn_opt_revision_t head_revision
3222 = { svn_opt_revision_head, { 0 } };
3224 svn_boolean_t timestamp_sleep = FALSE;
3226 apr_pool_t *subpool = svn_pool_create(pool);
3227 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3228 sizeof(const svn_client_copy_source_t *));
3230 if (src_paths->nelts > 1 && !move_as_child)
3231 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3234 for (i = 0; i < src_paths->nelts; i++)
3236 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3237 svn_client_copy_source_t *copy_source = apr_palloc(pool,
3238 sizeof(*copy_source));
3240 copy_source->path = src_path;
3241 copy_source->revision = &head_revision;
3242 copy_source->peg_revision = &head_revision;
3244 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3247 err = try_copy(×tamp_sleep,
3250 allow_mixed_revisions,
3253 FALSE /* ignore_externals */,
3254 FALSE /* pin_externals */,
3255 NULL /* externals_to_pin */,
3257 commit_callback, commit_baton,
3261 /* If the destination exists, try to move the sources as children of the
3263 if (move_as_child && err && (src_paths->nelts == 1)
3264 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3265 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3267 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3268 const char *src_basename;
3269 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3270 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3272 svn_error_clear(err);
3273 svn_pool_clear(subpool);
3275 src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3276 : svn_dirent_basename(src_path, pool);
3278 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3280 : svn_dirent_join(dst_path, src_basename, subpool);
3282 err = try_copy(×tamp_sleep,
3285 allow_mixed_revisions,
3288 FALSE /* ignore_externals */,
3289 FALSE /* pin_externals */,
3290 NULL /* externals_to_pin */,
3292 commit_callback, commit_baton,
3297 /* Sleep if required. DST_PATH is not a URL in these cases. */
3298 if (timestamp_sleep)
3299 svn_io_sleep_for_timestamps(dst_path, subpool);
3301 svn_pool_destroy(subpool);
3302 return svn_error_trace(err);