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);
181 /* The guts of do_wc_to_wc_copies */
183 do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
184 const apr_array_header_t *copy_pairs,
185 const char *dst_parent,
186 svn_client_ctx_t *ctx,
187 apr_pool_t *scratch_pool)
190 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
191 svn_error_t *err = SVN_NO_ERROR;
193 for (i = 0; i < copy_pairs->nelts; i++)
195 const char *dst_abspath;
196 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
197 svn_client__copy_pair_t *);
198 svn_pool_clear(iterpool);
200 /* Check for cancellation */
201 if (ctx->cancel_func)
202 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
204 /* Perform the copy */
205 dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
207 *timestamp_sleep = TRUE;
208 err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
209 FALSE /* metadata_only */,
210 ctx->cancel_func, ctx->cancel_baton,
211 ctx->notify_func2, ctx->notify_baton2, iterpool);
215 svn_pool_destroy(iterpool);
221 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
224 do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
225 const apr_array_header_t *copy_pairs,
226 svn_client_ctx_t *ctx,
229 const char *dst_parent, *dst_parent_abspath;
231 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
232 if (copy_pairs->nelts == 1)
233 dst_parent = svn_dirent_dirname(dst_parent, pool);
235 SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
237 SVN_WC__CALL_WITH_WRITE_LOCK(
238 do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
240 ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
245 /* The locked bit of do_wc_to_wc_moves. */
247 do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
248 const char *dst_parent_abspath,
249 svn_boolean_t lock_src,
250 svn_boolean_t lock_dst,
251 svn_boolean_t allow_mixed_revisions,
252 svn_boolean_t metadata_only,
253 svn_client_ctx_t *ctx,
254 apr_pool_t *scratch_pool)
256 const char *dst_abspath;
258 dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
261 SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
262 dst_abspath, metadata_only,
263 allow_mixed_revisions,
264 ctx->cancel_func, ctx->cancel_baton,
265 ctx->notify_func2, ctx->notify_baton2,
271 /* Wrapper to add an optional second lock */
273 do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
274 const char *dst_parent_abspath,
275 svn_boolean_t lock_src,
276 svn_boolean_t lock_dst,
277 svn_boolean_t allow_mixed_revisions,
278 svn_boolean_t metadata_only,
279 svn_client_ctx_t *ctx,
280 apr_pool_t *scratch_pool)
283 SVN_WC__CALL_WITH_WRITE_LOCK(
284 do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
285 lock_dst, allow_mixed_revisions,
288 ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
290 SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
291 lock_dst, allow_mixed_revisions,
298 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
299 afterwards. Use POOL for temporary allocations. */
301 do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
302 const apr_array_header_t *copy_pairs,
303 const char *dst_path,
304 svn_boolean_t allow_mixed_revisions,
305 svn_boolean_t metadata_only,
306 svn_client_ctx_t *ctx,
310 apr_pool_t *iterpool = svn_pool_create(pool);
311 svn_error_t *err = SVN_NO_ERROR;
313 for (i = 0; i < copy_pairs->nelts; i++)
315 const char *src_parent_abspath;
316 svn_boolean_t lock_src, lock_dst;
318 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
319 svn_client__copy_pair_t *);
320 svn_pool_clear(iterpool);
322 /* Check for cancellation */
323 if (ctx->cancel_func)
324 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
326 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
329 /* We now need to lock the right combination of batons.
331 1) src_parent == dst_parent
332 2) src_parent is parent of dst_parent
333 3) dst_parent is parent of src_parent
334 4) src_parent and dst_parent are disjoint
335 We can handle 1) as either 2) or 3) */
336 if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
337 || svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
343 else if (svn_dirent_is_child(pair->dst_parent_abspath,
356 *timestamp_sleep = TRUE;
358 /* Perform the copy and then the delete. */
360 SVN_WC__CALL_WITH_WRITE_LOCK(
361 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
363 allow_mixed_revisions,
366 ctx->wc_ctx, src_parent_abspath,
369 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
371 allow_mixed_revisions,
376 svn_pool_destroy(iterpool);
378 return svn_error_trace(err);
381 /* Verify that the destinations stored in COPY_PAIRS are valid working copy
382 destinations and set pair->dst_parent_abspath and pair->base_name for each
383 item to the resulting location if they do */
385 verify_wc_dsts(const apr_array_header_t *copy_pairs,
386 svn_boolean_t make_parents,
387 svn_boolean_t is_move,
388 svn_boolean_t metadata_only,
389 svn_client_ctx_t *ctx,
390 apr_pool_t *result_pool,
391 apr_pool_t *scratch_pool)
394 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
396 /* Check that DST does not exist, but its parent does */
397 for (i = 0; i < copy_pairs->nelts; i++)
399 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
400 svn_client__copy_pair_t *);
401 svn_node_kind_t dst_kind, dst_parent_kind;
403 svn_pool_clear(iterpool);
405 /* If DST_PATH does not exist, then its basename will become a new
406 file or dir added to its parent (possibly an implicit '.').
407 Else, just error out. */
408 SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
409 pair->dst_abspath_or_url,
410 FALSE /* show_deleted */,
411 TRUE /* show_hidden */,
413 if (dst_kind != svn_node_none)
415 svn_boolean_t is_excluded;
416 svn_boolean_t is_server_excluded;
418 SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
419 &is_server_excluded, ctx->wc_ctx,
420 pair->dst_abspath_or_url, FALSE,
423 if (is_excluded || is_server_excluded)
425 return svn_error_createf(
426 SVN_ERR_WC_OBSTRUCTED_UPDATE,
427 NULL, _("Path '%s' exists, but is excluded"),
428 svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
431 return svn_error_createf(
432 SVN_ERR_ENTRY_EXISTS, NULL,
433 _("Path '%s' already exists"),
434 svn_dirent_local_style(pair->dst_abspath_or_url,
438 /* Check that there is no unversioned obstruction */
440 dst_kind = svn_node_none;
442 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
445 if (dst_kind != svn_node_none)
448 && copy_pairs->nelts == 1
449 && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
450 svn_dirent_dirname(pair->dst_abspath_or_url,
455 apr_status_t apr_err;
456 /* We have a rename inside a directory, which might collide
457 just because the case insensivity of the filesystem makes
458 the source match the destination. */
460 SVN_ERR(svn_path_cstring_from_utf8(&dst,
461 pair->dst_abspath_or_url,
464 apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
465 APR_FILEPATH_TRUENAME, iterpool);
469 /* And now bring it back to our canonical format */
470 SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
471 dst = svn_dirent_canonicalize(dst, iterpool);
473 /* else: Don't report this error; just report the normal error */
475 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
477 /* Ok, we have a single case only rename. Get out of here */
478 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
479 pair->dst_abspath_or_url, result_pool);
481 svn_pool_destroy(iterpool);
486 return svn_error_createf(
487 SVN_ERR_ENTRY_EXISTS, NULL,
488 _("Path '%s' already exists as unversioned node"),
489 svn_dirent_local_style(pair->dst_abspath_or_url,
493 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
494 pair->dst_abspath_or_url, result_pool);
496 /* Make sure the destination parent is a directory and produce a clear
497 error message if it is not. */
498 SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
499 ctx->wc_ctx, pair->dst_parent_abspath,
502 if (make_parents && dst_parent_kind == svn_node_none)
504 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
505 TRUE, ctx, iterpool));
507 else if (dst_parent_kind != svn_node_dir)
509 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
510 _("Path '%s' is not a directory"),
511 svn_dirent_local_style(
512 pair->dst_parent_abspath, scratch_pool));
515 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
516 &dst_parent_kind, scratch_pool));
518 if (dst_parent_kind != svn_node_dir)
519 return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
520 _("Path '%s' is not a directory"),
521 svn_dirent_local_style(
522 pair->dst_parent_abspath, scratch_pool));
525 svn_pool_destroy(iterpool);
531 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
532 svn_boolean_t make_parents,
533 svn_boolean_t is_move,
534 svn_boolean_t metadata_only,
535 svn_client_ctx_t *ctx,
536 apr_pool_t *result_pool,
537 apr_pool_t *scratch_pool)
540 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
542 /* Check that all of our SRCs exist. */
543 for (i = 0; i < copy_pairs->nelts; i++)
545 svn_boolean_t deleted_ok;
546 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
547 svn_client__copy_pair_t *);
548 svn_pool_clear(iterpool);
550 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
551 || pair->src_op_revision.kind == svn_opt_revision_base);
553 /* Verify that SRC_PATH exists. */
554 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
555 pair->src_abspath_or_url,
556 deleted_ok, FALSE, iterpool));
557 if (pair->src_kind == svn_node_none)
558 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
559 _("Path '%s' does not exist"),
560 svn_dirent_local_style(
561 pair->src_abspath_or_url,
565 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
566 result_pool, iterpool));
568 svn_pool_destroy(iterpool);
574 /* Path-specific state used as part of path_driver_cb_baton. */
575 typedef struct path_driver_info_t
578 const char *src_path;
579 const char *dst_path;
580 svn_node_kind_t src_kind;
581 svn_revnum_t src_revnum;
582 svn_boolean_t resurrection;
583 svn_boolean_t dir_add;
584 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
585 } path_driver_info_t;
588 /* The baton used with the path_driver_cb_func() callback for a copy
589 or move operation. */
590 struct path_driver_cb_baton
592 /* The editor (and its state) used to perform the operation. */
593 const svn_delta_editor_t *editor;
596 /* A hash of path -> path_driver_info_t *'s. */
597 apr_hash_t *action_hash;
599 /* Whether the operation is a move or copy. */
600 svn_boolean_t is_move;
604 path_driver_cb_func(void **dir_baton,
606 void *callback_baton,
610 struct path_driver_cb_baton *cb_baton = callback_baton;
611 svn_boolean_t do_delete = FALSE, do_add = FALSE;
612 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
614 /* Initialize return value. */
617 /* This function should never get an empty PATH. We can neither
618 create nor delete the empty PATH, so if someone is calling us
619 with such, the code is just plain wrong. */
620 SVN_ERR_ASSERT(! svn_path_is_empty(path));
622 /* Check to see if we need to add the path as a directory. */
623 if (path_info->dir_add)
625 return cb_baton->editor->add_directory(path, parent_baton, NULL,
626 SVN_INVALID_REVNUM, pool,
630 /* If this is a resurrection, we know the source and dest paths are
631 the same, and that our driver will only be calling us once. */
632 if (path_info->resurrection)
634 /* If this is a move, we do nothing. Otherwise, we do the copy. */
635 if (! cb_baton->is_move)
638 /* Not a resurrection. */
641 /* If this is a move, we check PATH to see if it is the source
642 or the destination of the move. */
643 if (cb_baton->is_move)
645 if (strcmp(path_info->src_path, path) == 0)
650 /* Not a move? This must just be the copy addition. */
659 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
660 parent_baton, pool));
664 SVN_ERR(svn_path_check_valid(path, pool));
666 if (path_info->src_kind == svn_node_file)
669 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
671 path_info->src_revnum,
673 if (path_info->mergeinfo)
674 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
676 path_info->mergeinfo,
678 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
682 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
684 path_info->src_revnum,
686 if (path_info->mergeinfo)
687 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
689 path_info->mergeinfo,
697 /* Starting with the path DIR relative to the RA_SESSION's session
698 URL, work up through DIR's parents until an existing node is found.
699 Push each nonexistent path onto the array NEW_DIRS, allocating in
700 POOL. Raise an error if the existing node is not a directory.
702 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
703 ### implementation susceptible to race conditions. */
705 find_absent_parents1(svn_ra_session_t *ra_session,
707 apr_array_header_t *new_dirs,
710 svn_node_kind_t kind;
711 apr_pool_t *iterpool = svn_pool_create(pool);
713 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
716 while (kind == svn_node_none)
718 svn_pool_clear(iterpool);
720 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
721 dir = svn_dirent_dirname(dir, pool);
723 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
727 if (kind != svn_node_dir)
728 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
729 _("Path '%s' already exists, but is not a "
732 svn_pool_destroy(iterpool);
736 /* Starting with the URL *TOP_DST_URL which is also the root of
737 RA_SESSION, work up through its parents until an existing node is
738 found. Push each nonexistent URL onto the array NEW_DIRS,
739 allocating in POOL. Raise an error if the existing node is not a
742 Set *TOP_DST_URL and the RA session's root to the existing node's URL.
744 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
745 ### implementation susceptible to race conditions. */
747 find_absent_parents2(svn_ra_session_t *ra_session,
748 const char **top_dst_url,
749 apr_array_header_t *new_dirs,
752 const char *root_url = *top_dst_url;
753 svn_node_kind_t kind;
755 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
758 while (kind == svn_node_none)
760 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
761 root_url = svn_uri_dirname(root_url, pool);
763 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
764 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
768 if (kind != svn_node_dir)
769 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
770 _("Path '%s' already exists, but is not a directory"),
773 *top_dst_url = root_url;
778 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
779 svn_boolean_t make_parents,
780 const apr_hash_t *revprop_table,
781 svn_commit_callback2_t commit_callback,
783 svn_client_ctx_t *ctx,
784 svn_boolean_t is_move,
788 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
789 sizeof(const char *));
790 apr_hash_t *action_hash = apr_hash_make(pool);
791 apr_array_header_t *path_infos;
792 const char *top_url, *top_url_all, *top_url_dst;
793 const char *message, *repos_root;
794 svn_ra_session_t *ra_session = NULL;
795 const svn_delta_editor_t *editor;
797 struct path_driver_cb_baton cb_baton;
798 apr_array_header_t *new_dirs = NULL;
799 apr_hash_t *commit_revprops;
801 svn_client__copy_pair_t *first_pair =
802 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
804 /* Open an RA session to the first copy pair's destination. We'll
805 be verifying that every one of our copy source and destination
806 URLs is or is beneath this sucker's repository root URL as a form
807 of a cheap(ish) sanity check. */
808 SVN_ERR(svn_client_open_ra_session2(&ra_session,
809 first_pair->src_abspath_or_url, NULL,
811 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
813 /* Verify that sources and destinations are all at or under
814 REPOS_ROOT. While here, create a path_info struct for each
815 src/dst pair and initialize portions of it with normalized source
816 location information. */
817 path_infos = apr_array_make(pool, copy_pairs->nelts,
818 sizeof(path_driver_info_t *));
819 for (i = 0; i < copy_pairs->nelts; i++)
821 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
822 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
823 svn_client__copy_pair_t *);
824 apr_hash_t *mergeinfo;
826 /* Are the source and destination URLs at or under REPOS_ROOT? */
827 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
828 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
829 return svn_error_create
830 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
831 _("Source and destination URLs appear not to point to the "
832 "same repository."));
834 /* Run the history function to get the source's URL and revnum in the
835 operational revision. */
836 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
837 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
841 pair->src_abspath_or_url,
842 &pair->src_peg_revision,
843 &pair->src_op_revision, NULL,
846 /* Go ahead and grab mergeinfo from the source, too. */
847 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
848 SVN_ERR(svn_client__get_repos_mergeinfo(
849 &mergeinfo, ra_session,
850 pair->src_abspath_or_url, pair->src_revnum,
851 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
853 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
855 /* Plop an INFO structure onto our array thereof. */
856 info->src_url = pair->src_abspath_or_url;
857 info->src_revnum = pair->src_revnum;
858 info->resurrection = FALSE;
859 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
862 /* If this is a move, we have to open our session to the longest
863 path common to all SRC_URLS and DST_URLS in the repository so we
864 can do existence checks on all paths, and so we can operate on
865 all paths in the case of a move. But if this is *not* a move,
866 then opening our session at the longest path common to sources
867 *and* destinations might be an optimization when the user is
868 authorized to access all that stuff, but could cause the
869 operation to fail altogether otherwise. See issue #3242. */
870 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
872 top_url = is_move ? top_url_all : top_url_dst;
874 /* Check each src/dst pair for resurrection, and verify that TOP_URL
875 is anchored high enough to cover all the editor_t activities
876 required for this operation. */
877 for (i = 0; i < copy_pairs->nelts; i++)
879 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
880 svn_client__copy_pair_t *);
881 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
882 path_driver_info_t *);
884 /* Source and destination are the same? It's a resurrection. */
885 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
886 info->resurrection = TRUE;
888 /* We need to add each dst_URL, and (in a move) we'll need to
889 delete each src_URL. Our selection of TOP_URL so far ensures
890 that all our destination URLs (and source URLs, for moves)
891 are at least as deep as TOP_URL, but we need to make sure
892 that TOP_URL is an *ancestor* of all our to-be-edited paths.
894 Issue #683 is demonstrates this scenario. If you're
895 resurrecting a deleted item like this: 'svn cp -rN src_URL
896 dst_URL', then src_URL == dst_URL == top_url. In this
897 situation, we want to open an RA session to be at least the
898 *parent* of all three. */
899 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
900 && (strcmp(top_url, repos_root) != 0))
902 top_url = svn_uri_dirname(top_url, pool);
905 && (strcmp(top_url, pair->src_abspath_or_url) == 0)
906 && (strcmp(top_url, repos_root) != 0))
908 top_url = svn_uri_dirname(top_url, pool);
912 /* Point the RA session to our current TOP_URL. */
913 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
915 /* If we're allowed to create nonexistent parent directories of our
916 destinations, then make a list in NEW_DIRS of the parent
917 directories of the destination that don't yet exist. */
920 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
922 /* If this is a move, TOP_URL is at least the common ancestor of
923 all the paths (sources and destinations) involved. Assuming
924 the sources exist (which is fair, because if they don't, this
925 whole operation will fail anyway), TOP_URL must also exist.
926 So it's the paths between TOP_URL and the destinations which
927 we have to check for existence. But here, we take advantage
928 of the knowledge of our caller. We know that if there are
929 multiple copy/move operations being requested, then the
930 destinations of the copies/moves will all be siblings of one
931 another. Therefore, we need only to check for the
932 nonexistent paths between TOP_URL and *one* of our
933 destinations to find nonexistent parents of all of them. */
936 /* Imagine a situation where the user tries to copy an
937 existing source directory to nonexistent directory with
938 --parents options specified:
940 svn copy --parents URL/src URL/dst
942 where src exists and dst does not. If the dirname of the
943 destination path is equal to TOP_URL,
944 do not try to add dst to the NEW_DIRS list since it
945 will be added to the commit items array later in this
947 const char *dir = svn_uri_skip_ancestor(
949 svn_uri_dirname(first_pair->dst_abspath_or_url,
953 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
955 /* If, however, this is *not* a move, TOP_URL only points to the
956 common ancestor of our destination path(s), or possibly one
957 level higher. We'll need to do an existence crawl toward the
958 root of the repository, starting with one of our destinations
959 (see "... take advantage of the knowledge of our caller ..."
960 above), and possibly adjusting TOP_URL as we go. */
963 apr_array_header_t *new_urls =
964 apr_array_make(pool, 0, sizeof(const char *));
965 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
967 /* Convert absolute URLs into relpaths relative to TOP_URL. */
968 for (i = 0; i < new_urls->nelts; i++)
970 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
971 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
973 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
978 /* For each src/dst pair, check to see if that SRC_URL is a child of
979 the DST_URL (excepting the case where DST_URL is the repo root).
980 If it is, and the parent of DST_URL is the current TOP_URL, then we
981 need to reparent the session one directory higher, the parent of
983 for (i = 0; i < copy_pairs->nelts; i++)
985 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
986 svn_client__copy_pair_t *);
987 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
988 path_driver_info_t *);
989 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
990 pair->src_abspath_or_url,
993 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
994 && (relpath != NULL && *relpath != '\0'))
996 info->resurrection = TRUE;
997 top_url = svn_uri_dirname(top_url, pool);
998 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1002 /* Get the portions of the SRC and DST URLs that are relative to
1003 TOP_URL (URI-decoding them while we're at it), verify that the
1004 source exists and the proposed destination does not, and toss
1005 what we've learned into the INFO array. (For copies -- that is,
1006 non-moves -- the relative source URL NULL because it isn't a
1007 child of the TOP_URL at all. That's okay, we'll deal with
1009 for (i = 0; i < copy_pairs->nelts; i++)
1011 svn_client__copy_pair_t *pair =
1012 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1013 path_driver_info_t *info =
1014 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1015 svn_node_kind_t dst_kind;
1016 const char *src_rel, *dst_rel;
1018 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1021 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1022 &info->src_kind, pool));
1026 const char *old_url;
1029 SVN_ERR_ASSERT(! is_move);
1031 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1032 pair->src_abspath_or_url,
1034 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1035 &info->src_kind, pool));
1036 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1038 if (info->src_kind == svn_node_none)
1039 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1040 _("Path '%s' does not exist in revision %ld"),
1041 pair->src_abspath_or_url, pair->src_revnum);
1043 /* Figure out the basename that will result from this operation,
1044 and ensure that we aren't trying to overwrite existing paths. */
1045 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1046 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1048 if (dst_kind != svn_node_none)
1049 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1050 _("Path '%s' already exists"), dst_rel);
1052 /* More info for our INFO structure. */
1053 info->src_path = src_rel;
1054 info->dst_path = dst_rel;
1056 svn_hash_sets(action_hash, info->dst_path, info);
1057 if (is_move && (! info->resurrection))
1058 svn_hash_sets(action_hash, info->src_path, info);
1061 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1063 /* Produce a list of new paths to add, and provide it to the
1064 mechanism used to acquire a log message. */
1065 svn_client_commit_item3_t *item;
1066 const char *tmp_file;
1067 apr_array_header_t *commit_items
1068 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1070 /* Add any intermediate directories to the message */
1073 for (i = 0; i < new_dirs->nelts; i++)
1075 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1077 item = svn_client_commit_item3_create(pool);
1078 item->url = svn_path_url_add_component2(top_url, relpath, pool);
1079 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1080 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1084 for (i = 0; i < path_infos->nelts; i++)
1086 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1087 path_driver_info_t *);
1089 item = svn_client_commit_item3_create(pool);
1090 item->url = svn_path_url_add_component2(top_url, info->dst_path,
1092 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1093 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1095 if (is_move && (! info->resurrection))
1097 item = apr_pcalloc(pool, sizeof(*item));
1098 item->url = svn_path_url_add_component2(top_url, info->src_path,
1100 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1101 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1105 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1108 return SVN_NO_ERROR;
1113 /* Setup our PATHS for the path-based editor drive. */
1114 /* First any intermediate directories. */
1117 for (i = 0; i < new_dirs->nelts; i++)
1119 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1120 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1122 info->dst_path = relpath;
1123 info->dir_add = TRUE;
1125 APR_ARRAY_PUSH(paths, const char *) = relpath;
1126 svn_hash_sets(action_hash, relpath, info);
1130 /* Then our copy destinations and move sources (if any). */
1131 for (i = 0; i < path_infos->nelts; i++)
1133 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1134 path_driver_info_t *);
1136 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1137 if (is_move && (! info->resurrection))
1138 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1141 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1142 message, ctx, pool));
1144 /* Fetch RA commit editor. */
1145 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1146 svn_client__get_shim_callbacks(ctx->wc_ctx,
1148 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1152 NULL, TRUE, /* No lock tokens */
1155 /* Setup the callback baton. */
1156 cb_baton.editor = editor;
1157 cb_baton.edit_baton = edit_baton;
1158 cb_baton.action_hash = action_hash;
1159 cb_baton.is_move = is_move;
1161 /* Call the path-based editor driver. */
1162 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1163 path_driver_cb_func, &cb_baton, pool);
1166 /* At least try to abort the edit (and fs txn) before throwing err. */
1167 return svn_error_compose_create(
1169 editor->abort_edit(edit_baton, pool));
1172 /* Close the edit. */
1173 return svn_error_trace(editor->close_edit(edit_baton, pool));
1176 /* Baton for check_url_kind */
1177 struct check_url_kind_baton
1179 svn_ra_session_t *session;
1180 const char *repos_root_url;
1181 svn_boolean_t should_reparent;
1184 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1185 static svn_error_t *
1186 check_url_kind(void *baton,
1187 svn_node_kind_t *kind,
1189 svn_revnum_t revision,
1190 apr_pool_t *scratch_pool)
1192 struct check_url_kind_baton *cukb = baton;
1194 /* If we don't have a session or can't use the session, get one */
1195 if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1196 *kind = svn_node_none;
1199 cukb->should_reparent = TRUE;
1201 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1203 SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1204 kind, scratch_pool));
1207 return SVN_NO_ERROR;
1211 * COMMIT_INFO_P is ...
1212 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1213 * and each 'dst_abspath_or_url' is a URL.
1214 * MAKE_PARENTS is ...
1215 * REVPROP_TABLE is ...
1217 static svn_error_t *
1218 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1219 svn_boolean_t make_parents,
1220 const apr_hash_t *revprop_table,
1221 svn_commit_callback2_t commit_callback,
1223 svn_client_ctx_t *ctx,
1224 apr_pool_t *scratch_pool)
1226 const char *message;
1227 const char *top_src_path, *top_dst_url;
1228 struct check_url_kind_baton cukb;
1229 const char *top_src_abspath;
1230 svn_ra_session_t *ra_session;
1231 const svn_delta_editor_t *editor;
1232 apr_hash_t *relpath_map = NULL;
1234 svn_client__committables_t *committables;
1235 apr_array_header_t *commit_items;
1236 apr_pool_t *iterpool;
1237 apr_array_header_t *new_dirs = NULL;
1238 apr_hash_t *commit_revprops;
1239 svn_client__copy_pair_t *first_pair;
1240 apr_pool_t *session_pool = svn_pool_create(scratch_pool);
1243 /* Find the common root of all the source paths */
1244 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
1247 /* Do we need to lock the working copy? 1.6 didn't take a write
1248 lock, but what happens if the working copy changes during the copy
1251 iterpool = svn_pool_create(scratch_pool);
1253 /* Determine the longest common ancestor for the destinations, and open an RA
1254 session to that location. */
1255 /* ### But why start by getting the _parent_ of the first one? */
1256 /* --- That works because multiple destinations always point to the same
1257 * directory. I'm rather wondering why we need to find a common
1258 * destination parent here at all, instead of simply getting
1259 * top_dst_url from get_copy_pair_ancestors() above?
1260 * It looks like the entire block of code hanging off this comment
1262 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1263 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
1264 for (i = 1; i < copy_pairs->nelts; i++)
1266 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1267 svn_client__copy_pair_t *);
1268 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
1269 pair->dst_abspath_or_url,
1273 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
1275 /* Open a session to help while determining the exact targets */
1276 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1277 top_src_abspath, NULL,
1278 FALSE /* write_dav_props */,
1279 TRUE /* read_dav_props */,
1281 session_pool, session_pool));
1283 /* If requested, determine the nearest existing parent of the destination,
1284 and reparent the ra session there. */
1287 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
1288 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
1292 /* Figure out the basename that will result from each copy and check to make
1293 sure it doesn't exist already. */
1294 for (i = 0; i < copy_pairs->nelts; i++)
1296 svn_node_kind_t dst_kind;
1297 const char *dst_rel;
1298 svn_client__copy_pair_t *pair =
1299 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1301 svn_pool_clear(iterpool);
1302 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
1304 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1305 &dst_kind, iterpool));
1306 if (dst_kind != svn_node_none)
1308 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1309 _("Path '%s' already exists"),
1310 pair->dst_abspath_or_url);
1314 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1316 /* Produce a list of new paths to add, and provide it to the
1317 mechanism used to acquire a log message. */
1318 svn_client_commit_item3_t *item;
1319 const char *tmp_file;
1320 commit_items = apr_array_make(scratch_pool, copy_pairs->nelts,
1323 /* Add any intermediate directories to the message */
1326 for (i = 0; i < new_dirs->nelts; i++)
1328 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1330 item = svn_client_commit_item3_create(scratch_pool);
1332 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1333 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1337 for (i = 0; i < copy_pairs->nelts; i++)
1339 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1340 svn_client__copy_pair_t *);
1342 item = svn_client_commit_item3_create(scratch_pool);
1343 item->url = pair->dst_abspath_or_url;
1344 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1345 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1348 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1349 ctx, scratch_pool));
1352 svn_pool_destroy(iterpool);
1353 svn_pool_destroy(session_pool);
1354 return SVN_NO_ERROR;
1360 cukb.session = ra_session;
1361 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
1362 cukb.should_reparent = FALSE;
1364 /* Crawl the working copy for commit items. */
1365 /* ### TODO: Pass check_url_func for issue #3314 handling */
1366 SVN_ERR(svn_client__get_copy_committables(&committables,
1368 check_url_kind, &cukb,
1369 ctx, scratch_pool, iterpool));
1371 /* The committables are keyed by the repository root */
1372 commit_items = svn_hash_gets(committables->by_repository,
1373 cukb.repos_root_url);
1374 SVN_ERR_ASSERT(commit_items != NULL);
1376 if (cukb.should_reparent)
1377 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
1379 /* If we are creating intermediate directories, tack them onto the list
1383 for (i = 0; i < new_dirs->nelts; i++)
1385 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1386 svn_client_commit_item3_t *item;
1388 item = svn_client_commit_item3_create(scratch_pool);
1390 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1391 item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
1392 sizeof(svn_prop_t *));
1393 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1397 /* ### TODO: This extra loop would be unnecessary if this code lived
1398 ### in svn_client__get_copy_committables(), which is incidentally
1399 ### only used above (so should really be in this source file). */
1400 for (i = 0; i < copy_pairs->nelts; i++)
1402 apr_hash_t *mergeinfo, *wc_mergeinfo;
1403 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1404 svn_client__copy_pair_t *);
1405 svn_client_commit_item3_t *item =
1406 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1407 svn_client__pathrev_t *src_origin;
1409 svn_pool_clear(iterpool);
1411 SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
1412 pair->src_abspath_or_url,
1413 ctx, iterpool, iterpool));
1415 /* Set the mergeinfo for the destination to the combined merge
1416 info known to the WC and the repository. */
1417 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
1418 sizeof(svn_prop_t *));
1419 /* Repository mergeinfo (or NULL if it's locally added)... */
1421 SVN_ERR(svn_client__get_repos_mergeinfo(
1422 &mergeinfo, ra_session, src_origin->url, src_origin->rev,
1423 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
1426 /* ... and WC mergeinfo. */
1427 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
1428 pair->src_abspath_or_url,
1429 iterpool, iterpool));
1430 if (wc_mergeinfo && mergeinfo)
1431 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
1433 else if (! mergeinfo)
1434 mergeinfo = wc_mergeinfo;
1437 /* Push a mergeinfo prop representing MERGEINFO onto the
1438 * OUTGOING_PROP_CHANGES array. */
1440 svn_prop_t *mergeinfo_prop
1441 = apr_palloc(item->outgoing_prop_changes->pool,
1442 sizeof(svn_prop_t));
1443 svn_string_t *prop_value;
1445 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
1446 item->outgoing_prop_changes->pool));
1448 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
1449 mergeinfo_prop->value = prop_value;
1450 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
1455 /* Sort and condense our COMMIT_ITEMS. */
1456 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
1457 commit_items, scratch_pool));
1459 #ifdef ENABLE_EV2_SHIMS
1462 relpath_map = apr_hash_make(pool);
1463 for (i = 0; i < commit_items->nelts; i++)
1465 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
1466 svn_client_commit_item3_t *);
1467 const char *relpath;
1472 svn_pool_clear(iterpool);
1473 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
1474 ctx->wc_ctx, item->path, FALSE,
1475 scratch_pool, iterpool));
1477 svn_hash_sets(relpath_map, relpath, item->path);
1482 /* Close the initial session, to reopen a new session with commit handling */
1483 svn_pool_clear(session_pool);
1485 /* Open a new RA session to DST_URL. */
1486 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1489 session_pool, session_pool));
1491 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1492 message, ctx, session_pool));
1494 /* Fetch RA commit editor. */
1495 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1496 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
1498 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1502 TRUE, /* No lock tokens */
1505 /* Perform the commit. */
1506 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
1508 0, /* ### any notify_path_offset needed? */
1509 NULL, ctx, session_pool, session_pool),
1510 _("Commit failed (details follow):"));
1512 svn_pool_destroy(iterpool);
1513 svn_pool_destroy(session_pool);
1515 return SVN_NO_ERROR;
1518 /* A baton for notification_adjust_func(). */
1519 struct notification_adjust_baton
1521 svn_wc_notify_func2_t inner_func;
1523 const char *checkout_abspath;
1524 const char *final_abspath;
1527 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
1528 * baton is BATON->inner_baton) and adjusts the notification paths that
1529 * start with BATON->checkout_abspath to start instead with
1530 * BATON->final_abspath. */
1532 notification_adjust_func(void *baton,
1533 const svn_wc_notify_t *notify,
1536 struct notification_adjust_baton *nb = baton;
1537 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
1538 const char *relpath;
1540 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
1541 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
1544 nb->inner_func(nb->inner_baton, inner_notify, pool);
1547 /* Peform each individual copy operation for a repos -> wc copy. A
1548 helper for repos_to_wc_copy().
1550 Resolve PAIR->src_revnum to a real revision number if it isn't already. */
1551 static svn_error_t *
1552 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
1553 svn_client__copy_pair_t *pair,
1554 svn_boolean_t same_repositories,
1555 svn_boolean_t ignore_externals,
1556 svn_ra_session_t *ra_session,
1557 svn_client_ctx_t *ctx,
1560 apr_hash_t *src_mergeinfo;
1561 const char *dst_abspath = pair->dst_abspath_or_url;
1563 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
1565 if (!same_repositories && ctx->notify_func2)
1567 svn_wc_notify_t *notify;
1568 notify = svn_wc_create_notify_url(
1569 pair->src_abspath_or_url,
1570 svn_wc_notify_foreign_copy_begin,
1572 notify->kind = pair->src_kind;
1573 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1575 /* Allow a theoretical cancel to get through. */
1576 if (ctx->cancel_func)
1577 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1580 if (pair->src_kind == svn_node_dir)
1582 if (same_repositories)
1584 svn_boolean_t sleep_needed = FALSE;
1585 const char *tmpdir_abspath, *tmp_abspath;
1587 /* Find a temporary location in which to check out the copy source. */
1588 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
1591 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
1592 svn_io_file_del_on_close, pool, pool));
1594 /* Make a new checkout of the requested source. While doing so,
1595 * resolve pair->src_revnum to an actual revision number in case it
1596 * was until now 'invalid' meaning 'head'. Ask this function not to
1597 * sleep for timestamps, by passing a sleep_needed output param.
1598 * Send notifications for all nodes except the root node, and adjust
1599 * them to refer to the destination rather than this temporary path. */
1601 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
1602 void *old_notify_baton2 = ctx->notify_baton2;
1603 struct notification_adjust_baton nb;
1606 nb.inner_func = ctx->notify_func2;
1607 nb.inner_baton = ctx->notify_baton2;
1608 nb.checkout_abspath = tmp_abspath;
1609 nb.final_abspath = dst_abspath;
1610 ctx->notify_func2 = notification_adjust_func;
1611 ctx->notify_baton2 = &nb;
1613 err = svn_client__checkout_internal(&pair->src_revnum,
1616 &pair->src_peg_revision,
1617 &pair->src_op_revision,
1619 ignore_externals, FALSE,
1620 &sleep_needed, ctx, pool);
1622 ctx->notify_func2 = old_notify_func2;
1623 ctx->notify_baton2 = old_notify_baton2;
1628 *timestamp_sleep = TRUE;
1630 /* Schedule dst_path for addition in parent, with copy history.
1631 Don't send any notification here.
1632 Then remove the temporary checkout's .svn dir in preparation for
1633 moving the rest of it into the final destination. */
1634 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
1635 TRUE /* metadata_only */,
1636 ctx->cancel_func, ctx->cancel_baton,
1638 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
1639 FALSE, pool, pool));
1640 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
1647 /* Move the temporary disk tree into place. */
1648 SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
1652 *timestamp_sleep = TRUE;
1654 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
1656 &pair->src_peg_revision,
1657 &pair->src_op_revision,
1659 FALSE /* make_parents */,
1660 TRUE /* already_locked */,
1663 return SVN_NO_ERROR;
1665 } /* end directory case */
1667 else if (pair->src_kind == svn_node_file)
1669 apr_hash_t *new_props;
1670 const char *src_rel;
1671 svn_stream_t *new_base_contents = svn_stream_buffered(pool);
1673 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1674 pair->src_abspath_or_url,
1676 /* Fetch the file content. While doing so, resolve pair->src_revnum
1677 * to an actual revision number if it's 'invalid' meaning 'head'. */
1678 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
1680 &pair->src_revnum, &new_props, pool));
1682 if (new_props && ! same_repositories)
1683 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
1685 *timestamp_sleep = TRUE;
1687 SVN_ERR(svn_wc_add_repos_file4(
1688 ctx->wc_ctx, dst_abspath,
1689 new_base_contents, NULL, new_props, NULL,
1690 same_repositories ? pair->src_abspath_or_url : NULL,
1691 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
1692 ctx->cancel_func, ctx->cancel_baton,
1696 /* Record the implied mergeinfo (before the notification callback
1697 is invoked for the root node). */
1698 SVN_ERR(svn_client__get_repos_mergeinfo(
1699 &src_mergeinfo, ra_session,
1700 pair->src_abspath_or_url, pair->src_revnum,
1701 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1702 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
1704 /* Do our own notification for the root node, even if we could possibly
1705 have delegated it. See also issue #1552.
1707 ### Maybe this notification should mention the mergeinfo change. */
1708 if (ctx->notify_func2)
1710 svn_wc_notify_t *notify = svn_wc_create_notify(
1711 dst_abspath, svn_wc_notify_add, pool);
1712 notify->kind = pair->src_kind;
1713 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1716 return SVN_NO_ERROR;
1719 static svn_error_t *
1720 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
1721 const apr_array_header_t *copy_pairs,
1722 const char *top_dst_path,
1723 svn_boolean_t ignore_externals,
1724 svn_ra_session_t *ra_session,
1725 svn_client_ctx_t *ctx,
1726 apr_pool_t *scratch_pool)
1729 svn_boolean_t same_repositories;
1730 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1732 /* We've already checked for physical obstruction by a working file.
1733 But there could also be logical obstruction by an entry whose
1734 working file happens to be missing.*/
1735 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
1736 ctx, scratch_pool, iterpool));
1738 /* Decide whether the two repositories are the same or not. */
1740 svn_error_t *src_err, *dst_err;
1742 const char *parent_abspath;
1743 const char *src_uuid, *dst_uuid;
1745 /* Get the repository uuid of SRC_URL */
1746 src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
1747 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1748 return svn_error_trace(src_err);
1750 /* Get repository uuid of dst's parent directory, since dst may
1751 not exist. ### TODO: we should probably walk up the wc here,
1752 in case the parent dir has an imaginary URL. */
1753 if (copy_pairs->nelts == 1)
1754 parent = svn_dirent_dirname(top_dst_path, scratch_pool);
1756 parent = top_dst_path;
1758 SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
1759 dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
1760 parent_abspath, ctx,
1761 iterpool, iterpool);
1762 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1765 /* If either of the UUIDs are nonexistent, then at least one of
1766 the repositories must be very old. Rather than punish the
1767 user, just assume the repositories are different, so no
1768 copy-history is attempted. */
1769 if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
1770 same_repositories = FALSE;
1772 same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
1775 /* Perform the move for each of the copy_pairs. */
1776 for (i = 0; i < copy_pairs->nelts; i++)
1778 /* Check for cancellation */
1779 if (ctx->cancel_func)
1780 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1782 svn_pool_clear(iterpool);
1784 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
1785 APR_ARRAY_IDX(copy_pairs, i,
1786 svn_client__copy_pair_t *),
1789 ra_session, ctx, iterpool));
1791 svn_pool_destroy(iterpool);
1793 return SVN_NO_ERROR;
1796 static svn_error_t *
1797 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
1798 const apr_array_header_t *copy_pairs,
1799 svn_boolean_t make_parents,
1800 svn_boolean_t ignore_externals,
1801 svn_client_ctx_t *ctx,
1804 svn_ra_session_t *ra_session;
1805 const char *top_src_url, *top_dst_path;
1806 apr_pool_t *iterpool = svn_pool_create(pool);
1807 const char *lock_abspath;
1810 /* Get the real path for the source, based upon its peg revision. */
1811 for (i = 0; i < copy_pairs->nelts; i++)
1813 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1814 svn_client__copy_pair_t *);
1817 svn_pool_clear(iterpool);
1819 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
1821 pair->src_abspath_or_url,
1822 &pair->src_peg_revision,
1823 &pair->src_op_revision, NULL,
1826 pair->src_original = pair->src_abspath_or_url;
1827 pair->src_abspath_or_url = apr_pstrdup(pool, src);
1830 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
1832 lock_abspath = top_dst_path;
1833 if (copy_pairs->nelts == 1)
1835 top_src_url = svn_uri_dirname(top_src_url, pool);
1836 lock_abspath = svn_dirent_dirname(top_dst_path, pool);
1839 /* Open a repository session to the longest common src ancestor. We do not
1840 (yet) have a working copy, so we don't have a corresponding path and
1841 tempfiles cannot go into the admin area. */
1842 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
1845 /* Get the correct src path for the peg revision used, and verify that we
1846 aren't overwriting an existing path. */
1847 for (i = 0; i < copy_pairs->nelts; i++)
1849 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1850 svn_client__copy_pair_t *);
1851 svn_node_kind_t dst_parent_kind, dst_kind;
1852 const char *dst_parent;
1853 const char *src_rel;
1855 svn_pool_clear(iterpool);
1857 /* Next, make sure that the path exists in the repository. */
1858 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1859 pair->src_abspath_or_url,
1861 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1862 &pair->src_kind, pool));
1863 if (pair->src_kind == svn_node_none)
1865 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
1866 return svn_error_createf
1867 (SVN_ERR_FS_NOT_FOUND, NULL,
1868 _("Path '%s' not found in revision %ld"),
1869 pair->src_abspath_or_url, pair->src_revnum);
1871 return svn_error_createf
1872 (SVN_ERR_FS_NOT_FOUND, NULL,
1873 _("Path '%s' not found in head revision"),
1874 pair->src_abspath_or_url);
1877 /* Figure out about dst. */
1878 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1880 if (dst_kind != svn_node_none)
1882 return svn_error_createf(
1883 SVN_ERR_ENTRY_EXISTS, NULL,
1884 _("Path '%s' already exists"),
1885 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
1888 /* Make sure the destination parent is a directory and produce a clear
1889 error message if it is not. */
1890 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
1891 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
1892 if (make_parents && dst_parent_kind == svn_node_none)
1894 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
1897 else if (dst_parent_kind != svn_node_dir)
1899 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1900 _("Path '%s' is not a directory"),
1901 svn_dirent_local_style(dst_parent, pool));
1904 svn_pool_destroy(iterpool);
1906 SVN_WC__CALL_WITH_WRITE_LOCK(
1907 repos_to_wc_copy_locked(timestamp_sleep,
1908 copy_pairs, top_dst_path, ignore_externals,
1909 ra_session, ctx, pool),
1910 ctx->wc_ctx, lock_abspath, FALSE, pool);
1911 return SVN_NO_ERROR;
1914 #define NEED_REPOS_REVNUM(revision) \
1915 ((revision.kind != svn_opt_revision_unspecified) \
1916 && (revision.kind != svn_opt_revision_working))
1920 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
1921 * change *TIMESTAMP_SLEEP. This output will be valid even if the
1922 * function returns an error.
1924 * Perform all allocations in POOL.
1926 static svn_error_t *
1927 try_copy(svn_boolean_t *timestamp_sleep,
1928 const apr_array_header_t *sources,
1929 const char *dst_path_in,
1930 svn_boolean_t is_move,
1931 svn_boolean_t allow_mixed_revisions,
1932 svn_boolean_t metadata_only,
1933 svn_boolean_t make_parents,
1934 svn_boolean_t ignore_externals,
1935 const apr_hash_t *revprop_table,
1936 svn_commit_callback2_t commit_callback,
1938 svn_client_ctx_t *ctx,
1941 apr_array_header_t *copy_pairs =
1942 apr_array_make(pool, sources->nelts,
1943 sizeof(svn_client__copy_pair_t *));
1944 svn_boolean_t srcs_are_urls, dst_is_url;
1947 /* Are either of our paths URLs? Just check the first src_path. If
1948 there are more than one, we'll check for homogeneity among them
1950 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
1951 svn_client_copy_source_t *)->path);
1952 dst_is_url = svn_path_is_url(dst_path_in);
1954 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
1956 /* If we have multiple source paths, it implies the dst_path is a
1957 directory we are moving or copying into. Populate the COPY_PAIRS
1958 array to contain a destination path for each of the source paths. */
1959 if (sources->nelts > 1)
1961 apr_pool_t *iterpool = svn_pool_create(pool);
1963 for (i = 0; i < sources->nelts; i++)
1965 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
1966 svn_client_copy_source_t *);
1967 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1968 const char *src_basename;
1969 svn_boolean_t src_is_url = svn_path_is_url(source->path);
1971 svn_pool_clear(iterpool);
1975 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
1976 src_basename = svn_uri_basename(pair->src_abspath_or_url,
1981 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
1982 source->path, pool));
1983 src_basename = svn_dirent_basename(pair->src_abspath_or_url,
1987 pair->src_op_revision = *source->revision;
1988 pair->src_peg_revision = *source->peg_revision;
1990 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
1991 &pair->src_op_revision,
1996 /* Check to see if all the sources are urls or all working copy
1998 if (src_is_url != srcs_are_urls)
1999 return svn_error_create
2000 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2001 _("Cannot mix repository and working copy sources"));
2004 pair->dst_abspath_or_url =
2005 svn_path_url_add_component2(dst_path_in, src_basename, pool);
2007 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2008 src_basename, pool);
2009 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2012 svn_pool_destroy(iterpool);
2016 /* Only one source path. */
2017 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
2018 svn_client_copy_source_t *source =
2019 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2020 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2023 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2025 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2026 source->path, pool));
2027 pair->src_op_revision = *source->revision;
2028 pair->src_peg_revision = *source->peg_revision;
2030 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2031 &pair->src_op_revision,
2032 src_is_url, TRUE, pool));
2034 pair->dst_abspath_or_url = dst_path_in;
2035 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2038 if (!srcs_are_urls && !dst_is_url)
2040 apr_pool_t *iterpool = svn_pool_create(pool);
2042 for (i = 0; i < copy_pairs->nelts; i++)
2044 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2045 svn_client__copy_pair_t *);
2047 svn_pool_clear(iterpool);
2049 if (svn_dirent_is_child(pair->src_abspath_or_url,
2050 pair->dst_abspath_or_url, iterpool))
2051 return svn_error_createf
2052 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2053 _("Cannot copy path '%s' into its own child '%s'"),
2054 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2055 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2058 svn_pool_destroy(iterpool);
2061 /* A file external should not be moved since the file external is
2062 implemented as a switched file and it would delete the file the
2063 file external is switched to, which is not the behavior the user
2064 would probably want. */
2065 if (is_move && !srcs_are_urls)
2067 apr_pool_t *iterpool = svn_pool_create(pool);
2069 for (i = 0; i < copy_pairs->nelts; i++)
2071 svn_client__copy_pair_t *pair =
2072 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2073 svn_node_kind_t external_kind;
2074 const char *defining_abspath;
2076 svn_pool_clear(iterpool);
2078 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2079 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2080 NULL, NULL, NULL, ctx->wc_ctx,
2081 pair->src_abspath_or_url,
2082 pair->src_abspath_or_url, TRUE,
2083 iterpool, iterpool));
2085 if (external_kind != svn_node_none)
2086 return svn_error_createf(
2087 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2089 _("Cannot move the external at '%s'; please "
2090 "edit the svn:externals property on '%s'."),
2091 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2092 svn_dirent_local_style(defining_abspath, pool));
2094 svn_pool_destroy(iterpool);
2099 /* Disallow moves between the working copy and the repository. */
2100 if (srcs_are_urls != dst_is_url)
2102 return svn_error_create
2103 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2104 _("Moves between the working copy and the repository are not "
2108 /* Disallow moving any path/URL onto or into itself. */
2109 for (i = 0; i < copy_pairs->nelts; i++)
2111 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2112 svn_client__copy_pair_t *);
2114 if (strcmp(pair->src_abspath_or_url,
2115 pair->dst_abspath_or_url) == 0)
2116 return svn_error_createf(
2117 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2119 _("Cannot move URL '%s' into itself") :
2120 _("Cannot move path '%s' into itself"),
2122 pair->src_abspath_or_url :
2123 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2130 /* If we are doing a wc->* copy, but with an operational revision
2131 other than the working copy revision, we are really doing a
2132 repo->* copy, because we're going to need to get the rev from the
2135 svn_boolean_t need_repos_op_rev = FALSE;
2136 svn_boolean_t need_repos_peg_rev = FALSE;
2138 /* Check to see if any revision is something other than
2139 svn_opt_revision_unspecified or svn_opt_revision_working. */
2140 for (i = 0; i < copy_pairs->nelts; i++)
2142 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2143 svn_client__copy_pair_t *);
2145 if (NEED_REPOS_REVNUM(pair->src_op_revision))
2146 need_repos_op_rev = TRUE;
2148 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
2149 need_repos_peg_rev = TRUE;
2151 if (need_repos_op_rev || need_repos_peg_rev)
2155 if (need_repos_op_rev || need_repos_peg_rev)
2157 apr_pool_t *iterpool = svn_pool_create(pool);
2159 for (i = 0; i < copy_pairs->nelts; i++)
2161 const char *copyfrom_repos_root_url;
2162 const char *copyfrom_repos_relpath;
2164 svn_revnum_t copyfrom_rev;
2165 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2166 svn_client__copy_pair_t *);
2168 svn_pool_clear(iterpool);
2170 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2172 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev,
2173 ©from_repos_relpath,
2174 ©from_repos_root_url,
2177 pair->src_abspath_or_url,
2178 TRUE, iterpool, iterpool));
2180 if (copyfrom_repos_relpath)
2181 url = svn_path_url_add_component2(copyfrom_repos_root_url,
2182 copyfrom_repos_relpath,
2185 return svn_error_createf
2186 (SVN_ERR_ENTRY_MISSING_URL, NULL,
2187 _("'%s' does not have a URL associated with it"),
2188 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2190 pair->src_abspath_or_url = url;
2192 if (!need_repos_peg_rev
2193 || pair->src_peg_revision.kind == svn_opt_revision_base)
2195 /* Default the peg revision to that of the WC entry. */
2196 pair->src_peg_revision.kind = svn_opt_revision_number;
2197 pair->src_peg_revision.value.number = copyfrom_rev;
2200 if (pair->src_op_revision.kind == svn_opt_revision_base)
2202 /* Use the entry's revision as the operational rev. */
2203 pair->src_op_revision.kind = svn_opt_revision_number;
2204 pair->src_op_revision.value.number = copyfrom_rev;
2208 svn_pool_destroy(iterpool);
2209 srcs_are_urls = TRUE;
2214 /* Now, call the right handler for the operation. */
2215 if ((! srcs_are_urls) && (! dst_is_url))
2217 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
2218 metadata_only, ctx, pool, pool));
2220 /* Copy or move all targets. */
2222 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
2223 copy_pairs, dst_path_in,
2224 allow_mixed_revisions,
2229 /* We ignore these values, so assert the default value */
2230 SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
2231 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
2232 copy_pairs, ctx, pool));
2235 else if ((! srcs_are_urls) && (dst_is_url))
2237 return svn_error_trace(
2238 wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
2239 commit_callback, commit_baton, ctx, pool));
2241 else if ((srcs_are_urls) && (! dst_is_url))
2243 return svn_error_trace(
2244 repos_to_wc_copy(timestamp_sleep,
2245 copy_pairs, make_parents, ignore_externals,
2250 return svn_error_trace(
2251 repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
2252 commit_callback, commit_baton, ctx, is_move,
2259 /* Public Interfaces */
2261 svn_client_copy6(const apr_array_header_t *sources,
2262 const char *dst_path,
2263 svn_boolean_t copy_as_child,
2264 svn_boolean_t make_parents,
2265 svn_boolean_t ignore_externals,
2266 const apr_hash_t *revprop_table,
2267 svn_commit_callback2_t commit_callback,
2269 svn_client_ctx_t *ctx,
2273 svn_boolean_t timestamp_sleep = FALSE;
2274 apr_pool_t *subpool = svn_pool_create(pool);
2276 if (sources->nelts > 1 && !copy_as_child)
2277 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2280 err = try_copy(×tamp_sleep,
2282 FALSE /* is_move */,
2283 TRUE /* allow_mixed_revisions */,
2284 FALSE /* metadata_only */,
2288 commit_callback, commit_baton,
2292 /* If the destination exists, try to copy the sources as children of the
2294 if (copy_as_child && err && (sources->nelts == 1)
2295 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2296 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2298 const char *src_path = APR_ARRAY_IDX(sources, 0,
2299 svn_client_copy_source_t *)->path;
2300 const char *src_basename;
2301 svn_boolean_t src_is_url = svn_path_is_url(src_path);
2302 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2304 svn_error_clear(err);
2305 svn_pool_clear(subpool);
2307 src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
2308 : svn_dirent_basename(src_path, subpool);
2310 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2312 : svn_dirent_join(dst_path, src_basename, subpool);
2314 err = try_copy(×tamp_sleep,
2316 FALSE /* is_move */,
2317 TRUE /* allow_mixed_revisions */,
2318 FALSE /* metadata_only */,
2322 commit_callback, commit_baton,
2327 /* Sleep if required. DST_PATH is not a URL in these cases. */
2328 if (timestamp_sleep)
2329 svn_io_sleep_for_timestamps(dst_path, subpool);
2331 svn_pool_destroy(subpool);
2332 return svn_error_trace(err);
2337 svn_client_move7(const apr_array_header_t *src_paths,
2338 const char *dst_path,
2339 svn_boolean_t move_as_child,
2340 svn_boolean_t make_parents,
2341 svn_boolean_t allow_mixed_revisions,
2342 svn_boolean_t metadata_only,
2343 const apr_hash_t *revprop_table,
2344 svn_commit_callback2_t commit_callback,
2346 svn_client_ctx_t *ctx,
2349 const svn_opt_revision_t head_revision
2350 = { svn_opt_revision_head, { 0 } };
2352 svn_boolean_t timestamp_sleep = FALSE;
2354 apr_pool_t *subpool = svn_pool_create(pool);
2355 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
2356 sizeof(const svn_client_copy_source_t *));
2358 if (src_paths->nelts > 1 && !move_as_child)
2359 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2362 for (i = 0; i < src_paths->nelts; i++)
2364 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
2365 svn_client_copy_source_t *copy_source = apr_palloc(pool,
2366 sizeof(*copy_source));
2368 copy_source->path = src_path;
2369 copy_source->revision = &head_revision;
2370 copy_source->peg_revision = &head_revision;
2372 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
2375 err = try_copy(×tamp_sleep,
2378 allow_mixed_revisions,
2381 FALSE /* ignore_externals */,
2383 commit_callback, commit_baton,
2387 /* If the destination exists, try to move the sources as children of the
2389 if (move_as_child && err && (src_paths->nelts == 1)
2390 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2391 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2393 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
2394 const char *src_basename;
2395 svn_boolean_t src_is_url = svn_path_is_url(src_path);
2396 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2398 svn_error_clear(err);
2399 svn_pool_clear(subpool);
2401 src_basename = src_is_url ? svn_uri_basename(src_path, pool)
2402 : svn_dirent_basename(src_path, pool);
2404 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2406 : svn_dirent_join(dst_path, src_basename, subpool);
2408 err = try_copy(×tamp_sleep,
2411 allow_mixed_revisions,
2414 FALSE /* ignore_externals */,
2416 commit_callback, commit_baton,
2421 /* Sleep if required. DST_PATH is not a URL in these cases. */
2422 if (timestamp_sleep)
2423 svn_io_sleep_for_timestamps(dst_path, subpool);
2425 svn_pool_destroy(subpool);
2426 return svn_error_trace(err);