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;
317 const char *src_wcroot_abspath;
318 const char *dst_wcroot_abspath;
320 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
321 svn_client__copy_pair_t *);
322 svn_pool_clear(iterpool);
324 /* Check for cancellation */
325 if (ctx->cancel_func)
326 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
328 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
331 SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
332 ctx->wc_ctx, src_parent_abspath,
333 iterpool, iterpool));
334 SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
335 ctx->wc_ctx, pair->dst_parent_abspath,
336 iterpool, iterpool));
338 /* We now need to lock the right combination of batons.
340 1) src_parent == dst_parent
341 2) src_parent is parent of dst_parent
342 3) dst_parent is parent of src_parent
343 4) src_parent and dst_parent are disjoint
344 We can handle 1) as either 2) or 3) */
345 if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
346 || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
348 && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
354 else if (svn_dirent_is_child(pair->dst_parent_abspath,
355 src_parent_abspath, NULL)
356 && !svn_dirent_is_child(pair->dst_parent_abspath,
357 src_wcroot_abspath, NULL))
368 *timestamp_sleep = TRUE;
370 /* Perform the copy and then the delete. */
372 SVN_WC__CALL_WITH_WRITE_LOCK(
373 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
375 allow_mixed_revisions,
378 ctx->wc_ctx, src_parent_abspath,
381 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
383 allow_mixed_revisions,
388 svn_pool_destroy(iterpool);
390 return svn_error_trace(err);
393 /* Verify that the destinations stored in COPY_PAIRS are valid working copy
394 destinations and set pair->dst_parent_abspath and pair->base_name for each
395 item to the resulting location if they do */
397 verify_wc_dsts(const apr_array_header_t *copy_pairs,
398 svn_boolean_t make_parents,
399 svn_boolean_t is_move,
400 svn_boolean_t metadata_only,
401 svn_client_ctx_t *ctx,
402 apr_pool_t *result_pool,
403 apr_pool_t *scratch_pool)
406 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
408 /* Check that DST does not exist, but its parent does */
409 for (i = 0; i < copy_pairs->nelts; i++)
411 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
412 svn_client__copy_pair_t *);
413 svn_node_kind_t dst_kind, dst_parent_kind;
415 svn_pool_clear(iterpool);
417 /* If DST_PATH does not exist, then its basename will become a new
418 file or dir added to its parent (possibly an implicit '.').
419 Else, just error out. */
420 SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
421 pair->dst_abspath_or_url,
422 FALSE /* show_deleted */,
423 TRUE /* show_hidden */,
425 if (dst_kind != svn_node_none)
427 svn_boolean_t is_excluded;
428 svn_boolean_t is_server_excluded;
430 SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
431 &is_server_excluded, ctx->wc_ctx,
432 pair->dst_abspath_or_url, FALSE,
435 if (is_excluded || is_server_excluded)
437 return svn_error_createf(
438 SVN_ERR_WC_OBSTRUCTED_UPDATE,
439 NULL, _("Path '%s' exists, but is excluded"),
440 svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
443 return svn_error_createf(
444 SVN_ERR_ENTRY_EXISTS, NULL,
445 _("Path '%s' already exists"),
446 svn_dirent_local_style(pair->dst_abspath_or_url,
450 /* Check that there is no unversioned obstruction */
452 dst_kind = svn_node_none;
454 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
457 if (dst_kind != svn_node_none)
460 && copy_pairs->nelts == 1
461 && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
462 svn_dirent_dirname(pair->dst_abspath_or_url,
467 apr_status_t apr_err;
468 /* We have a rename inside a directory, which might collide
469 just because the case insensivity of the filesystem makes
470 the source match the destination. */
472 SVN_ERR(svn_path_cstring_from_utf8(&dst,
473 pair->dst_abspath_or_url,
476 apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
477 APR_FILEPATH_TRUENAME, iterpool);
481 /* And now bring it back to our canonical format */
482 SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
483 dst = svn_dirent_canonicalize(dst, iterpool);
485 /* else: Don't report this error; just report the normal error */
487 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
489 /* Ok, we have a single case only rename. Get out of here */
490 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
491 pair->dst_abspath_or_url, result_pool);
493 svn_pool_destroy(iterpool);
498 return svn_error_createf(
499 SVN_ERR_ENTRY_EXISTS, NULL,
500 _("Path '%s' already exists as unversioned node"),
501 svn_dirent_local_style(pair->dst_abspath_or_url,
505 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
506 pair->dst_abspath_or_url, result_pool);
508 /* Make sure the destination parent is a directory and produce a clear
509 error message if it is not. */
510 SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
511 ctx->wc_ctx, pair->dst_parent_abspath,
514 if (make_parents && dst_parent_kind == svn_node_none)
516 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
517 TRUE, ctx, iterpool));
519 else if (dst_parent_kind != svn_node_dir)
521 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
522 _("Path '%s' is not a directory"),
523 svn_dirent_local_style(
524 pair->dst_parent_abspath, scratch_pool));
527 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
528 &dst_parent_kind, scratch_pool));
530 if (dst_parent_kind != svn_node_dir)
531 return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
532 _("Path '%s' is not a directory"),
533 svn_dirent_local_style(
534 pair->dst_parent_abspath, scratch_pool));
537 svn_pool_destroy(iterpool);
543 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
544 svn_boolean_t make_parents,
545 svn_boolean_t is_move,
546 svn_boolean_t metadata_only,
547 svn_client_ctx_t *ctx,
548 apr_pool_t *result_pool,
549 apr_pool_t *scratch_pool)
552 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
554 /* Check that all of our SRCs exist. */
555 for (i = 0; i < copy_pairs->nelts; i++)
557 svn_boolean_t deleted_ok;
558 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
559 svn_client__copy_pair_t *);
560 svn_pool_clear(iterpool);
562 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
563 || pair->src_op_revision.kind == svn_opt_revision_base);
565 /* Verify that SRC_PATH exists. */
566 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
567 pair->src_abspath_or_url,
568 deleted_ok, FALSE, iterpool));
569 if (pair->src_kind == svn_node_none)
570 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
571 _("Path '%s' does not exist"),
572 svn_dirent_local_style(
573 pair->src_abspath_or_url,
577 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
578 result_pool, iterpool));
580 svn_pool_destroy(iterpool);
586 /* Path-specific state used as part of path_driver_cb_baton. */
587 typedef struct path_driver_info_t
590 const char *src_path;
591 const char *dst_path;
592 svn_node_kind_t src_kind;
593 svn_revnum_t src_revnum;
594 svn_boolean_t resurrection;
595 svn_boolean_t dir_add;
596 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
597 } path_driver_info_t;
600 /* The baton used with the path_driver_cb_func() callback for a copy
601 or move operation. */
602 struct path_driver_cb_baton
604 /* The editor (and its state) used to perform the operation. */
605 const svn_delta_editor_t *editor;
608 /* A hash of path -> path_driver_info_t *'s. */
609 apr_hash_t *action_hash;
611 /* Whether the operation is a move or copy. */
612 svn_boolean_t is_move;
616 path_driver_cb_func(void **dir_baton,
618 void *callback_baton,
622 struct path_driver_cb_baton *cb_baton = callback_baton;
623 svn_boolean_t do_delete = FALSE, do_add = FALSE;
624 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
626 /* Initialize return value. */
629 /* This function should never get an empty PATH. We can neither
630 create nor delete the empty PATH, so if someone is calling us
631 with such, the code is just plain wrong. */
632 SVN_ERR_ASSERT(! svn_path_is_empty(path));
634 /* Check to see if we need to add the path as a directory. */
635 if (path_info->dir_add)
637 return cb_baton->editor->add_directory(path, parent_baton, NULL,
638 SVN_INVALID_REVNUM, pool,
642 /* If this is a resurrection, we know the source and dest paths are
643 the same, and that our driver will only be calling us once. */
644 if (path_info->resurrection)
646 /* If this is a move, we do nothing. Otherwise, we do the copy. */
647 if (! cb_baton->is_move)
650 /* Not a resurrection. */
653 /* If this is a move, we check PATH to see if it is the source
654 or the destination of the move. */
655 if (cb_baton->is_move)
657 if (strcmp(path_info->src_path, path) == 0)
662 /* Not a move? This must just be the copy addition. */
671 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
672 parent_baton, pool));
676 SVN_ERR(svn_path_check_valid(path, pool));
678 if (path_info->src_kind == svn_node_file)
681 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
683 path_info->src_revnum,
685 if (path_info->mergeinfo)
686 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
688 path_info->mergeinfo,
690 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
694 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
696 path_info->src_revnum,
698 if (path_info->mergeinfo)
699 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
701 path_info->mergeinfo,
709 /* Starting with the path DIR relative to the RA_SESSION's session
710 URL, work up through DIR's parents until an existing node is found.
711 Push each nonexistent path onto the array NEW_DIRS, allocating in
712 POOL. Raise an error if the existing node is not a directory.
714 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
715 ### implementation susceptible to race conditions. */
717 find_absent_parents1(svn_ra_session_t *ra_session,
719 apr_array_header_t *new_dirs,
722 svn_node_kind_t kind;
723 apr_pool_t *iterpool = svn_pool_create(pool);
725 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
728 while (kind == svn_node_none)
730 svn_pool_clear(iterpool);
732 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
733 dir = svn_dirent_dirname(dir, pool);
735 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
739 if (kind != svn_node_dir)
740 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
741 _("Path '%s' already exists, but is not a "
744 svn_pool_destroy(iterpool);
748 /* Starting with the URL *TOP_DST_URL which is also the root of
749 RA_SESSION, work up through its parents until an existing node is
750 found. Push each nonexistent URL onto the array NEW_DIRS,
751 allocating in POOL. Raise an error if the existing node is not a
754 Set *TOP_DST_URL and the RA session's root to the existing node's URL.
756 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
757 ### implementation susceptible to race conditions. */
759 find_absent_parents2(svn_ra_session_t *ra_session,
760 const char **top_dst_url,
761 apr_array_header_t *new_dirs,
764 const char *root_url = *top_dst_url;
765 svn_node_kind_t kind;
767 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
770 while (kind == svn_node_none)
772 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
773 root_url = svn_uri_dirname(root_url, pool);
775 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
776 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
780 if (kind != svn_node_dir)
781 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
782 _("Path '%s' already exists, but is not a directory"),
785 *top_dst_url = root_url;
790 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
791 svn_boolean_t make_parents,
792 const apr_hash_t *revprop_table,
793 svn_commit_callback2_t commit_callback,
795 svn_client_ctx_t *ctx,
796 svn_boolean_t is_move,
800 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
801 sizeof(const char *));
802 apr_hash_t *action_hash = apr_hash_make(pool);
803 apr_array_header_t *path_infos;
804 const char *top_url, *top_url_all, *top_url_dst;
805 const char *message, *repos_root;
806 svn_ra_session_t *ra_session = NULL;
807 const svn_delta_editor_t *editor;
809 struct path_driver_cb_baton cb_baton;
810 apr_array_header_t *new_dirs = NULL;
811 apr_hash_t *commit_revprops;
813 svn_client__copy_pair_t *first_pair =
814 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
816 /* Open an RA session to the first copy pair's destination. We'll
817 be verifying that every one of our copy source and destination
818 URLs is or is beneath this sucker's repository root URL as a form
819 of a cheap(ish) sanity check. */
820 SVN_ERR(svn_client_open_ra_session2(&ra_session,
821 first_pair->src_abspath_or_url, NULL,
823 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
825 /* Verify that sources and destinations are all at or under
826 REPOS_ROOT. While here, create a path_info struct for each
827 src/dst pair and initialize portions of it with normalized source
828 location information. */
829 path_infos = apr_array_make(pool, copy_pairs->nelts,
830 sizeof(path_driver_info_t *));
831 for (i = 0; i < copy_pairs->nelts; i++)
833 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
834 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
835 svn_client__copy_pair_t *);
836 apr_hash_t *mergeinfo;
838 /* Are the source and destination URLs at or under REPOS_ROOT? */
839 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
840 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
841 return svn_error_create
842 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
843 _("Source and destination URLs appear not to point to the "
844 "same repository."));
846 /* Run the history function to get the source's URL and revnum in the
847 operational revision. */
848 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
849 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
853 pair->src_abspath_or_url,
854 &pair->src_peg_revision,
855 &pair->src_op_revision, NULL,
858 /* Go ahead and grab mergeinfo from the source, too. */
859 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
860 SVN_ERR(svn_client__get_repos_mergeinfo(
861 &mergeinfo, ra_session,
862 pair->src_abspath_or_url, pair->src_revnum,
863 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
865 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
867 /* Plop an INFO structure onto our array thereof. */
868 info->src_url = pair->src_abspath_or_url;
869 info->src_revnum = pair->src_revnum;
870 info->resurrection = FALSE;
871 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
874 /* If this is a move, we have to open our session to the longest
875 path common to all SRC_URLS and DST_URLS in the repository so we
876 can do existence checks on all paths, and so we can operate on
877 all paths in the case of a move. But if this is *not* a move,
878 then opening our session at the longest path common to sources
879 *and* destinations might be an optimization when the user is
880 authorized to access all that stuff, but could cause the
881 operation to fail altogether otherwise. See issue #3242. */
882 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
884 top_url = is_move ? top_url_all : top_url_dst;
886 /* Check each src/dst pair for resurrection, and verify that TOP_URL
887 is anchored high enough to cover all the editor_t activities
888 required for this operation. */
889 for (i = 0; i < copy_pairs->nelts; i++)
891 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
892 svn_client__copy_pair_t *);
893 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
894 path_driver_info_t *);
896 /* Source and destination are the same? It's a resurrection. */
897 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
898 info->resurrection = TRUE;
900 /* We need to add each dst_URL, and (in a move) we'll need to
901 delete each src_URL. Our selection of TOP_URL so far ensures
902 that all our destination URLs (and source URLs, for moves)
903 are at least as deep as TOP_URL, but we need to make sure
904 that TOP_URL is an *ancestor* of all our to-be-edited paths.
906 Issue #683 is demonstrates this scenario. If you're
907 resurrecting a deleted item like this: 'svn cp -rN src_URL
908 dst_URL', then src_URL == dst_URL == top_url. In this
909 situation, we want to open an RA session to be at least the
910 *parent* of all three. */
911 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
912 && (strcmp(top_url, repos_root) != 0))
914 top_url = svn_uri_dirname(top_url, pool);
917 && (strcmp(top_url, pair->src_abspath_or_url) == 0)
918 && (strcmp(top_url, repos_root) != 0))
920 top_url = svn_uri_dirname(top_url, pool);
924 /* Point the RA session to our current TOP_URL. */
925 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
927 /* If we're allowed to create nonexistent parent directories of our
928 destinations, then make a list in NEW_DIRS of the parent
929 directories of the destination that don't yet exist. */
932 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
934 /* If this is a move, TOP_URL is at least the common ancestor of
935 all the paths (sources and destinations) involved. Assuming
936 the sources exist (which is fair, because if they don't, this
937 whole operation will fail anyway), TOP_URL must also exist.
938 So it's the paths between TOP_URL and the destinations which
939 we have to check for existence. But here, we take advantage
940 of the knowledge of our caller. We know that if there are
941 multiple copy/move operations being requested, then the
942 destinations of the copies/moves will all be siblings of one
943 another. Therefore, we need only to check for the
944 nonexistent paths between TOP_URL and *one* of our
945 destinations to find nonexistent parents of all of them. */
948 /* Imagine a situation where the user tries to copy an
949 existing source directory to nonexistent directory with
950 --parents options specified:
952 svn copy --parents URL/src URL/dst
954 where src exists and dst does not. If the dirname of the
955 destination path is equal to TOP_URL,
956 do not try to add dst to the NEW_DIRS list since it
957 will be added to the commit items array later in this
959 const char *dir = svn_uri_skip_ancestor(
961 svn_uri_dirname(first_pair->dst_abspath_or_url,
965 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
967 /* If, however, this is *not* a move, TOP_URL only points to the
968 common ancestor of our destination path(s), or possibly one
969 level higher. We'll need to do an existence crawl toward the
970 root of the repository, starting with one of our destinations
971 (see "... take advantage of the knowledge of our caller ..."
972 above), and possibly adjusting TOP_URL as we go. */
975 apr_array_header_t *new_urls =
976 apr_array_make(pool, 0, sizeof(const char *));
977 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
979 /* Convert absolute URLs into relpaths relative to TOP_URL. */
980 for (i = 0; i < new_urls->nelts; i++)
982 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
983 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
985 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
990 /* For each src/dst pair, check to see if that SRC_URL is a child of
991 the DST_URL (excepting the case where DST_URL is the repo root).
992 If it is, and the parent of DST_URL is the current TOP_URL, then we
993 need to reparent the session one directory higher, the parent of
995 for (i = 0; i < copy_pairs->nelts; i++)
997 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
998 svn_client__copy_pair_t *);
999 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1000 path_driver_info_t *);
1001 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1002 pair->src_abspath_or_url,
1005 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1006 && (relpath != NULL && *relpath != '\0'))
1008 info->resurrection = TRUE;
1009 top_url = svn_uri_dirname(top_url, pool);
1010 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1014 /* Get the portions of the SRC and DST URLs that are relative to
1015 TOP_URL (URI-decoding them while we're at it), verify that the
1016 source exists and the proposed destination does not, and toss
1017 what we've learned into the INFO array. (For copies -- that is,
1018 non-moves -- the relative source URL NULL because it isn't a
1019 child of the TOP_URL at all. That's okay, we'll deal with
1021 for (i = 0; i < copy_pairs->nelts; i++)
1023 svn_client__copy_pair_t *pair =
1024 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1025 path_driver_info_t *info =
1026 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1027 svn_node_kind_t dst_kind;
1028 const char *src_rel, *dst_rel;
1030 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1033 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1034 &info->src_kind, pool));
1038 const char *old_url;
1041 SVN_ERR_ASSERT(! is_move);
1043 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1044 pair->src_abspath_or_url,
1046 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1047 &info->src_kind, pool));
1048 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1050 if (info->src_kind == svn_node_none)
1051 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1052 _("Path '%s' does not exist in revision %ld"),
1053 pair->src_abspath_or_url, pair->src_revnum);
1055 /* Figure out the basename that will result from this operation,
1056 and ensure that we aren't trying to overwrite existing paths. */
1057 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1058 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1060 if (dst_kind != svn_node_none)
1061 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1062 _("Path '%s' already exists"), dst_rel);
1064 /* More info for our INFO structure. */
1065 info->src_path = src_rel;
1066 info->dst_path = dst_rel;
1068 svn_hash_sets(action_hash, info->dst_path, info);
1069 if (is_move && (! info->resurrection))
1070 svn_hash_sets(action_hash, info->src_path, info);
1073 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1075 /* Produce a list of new paths to add, and provide it to the
1076 mechanism used to acquire a log message. */
1077 svn_client_commit_item3_t *item;
1078 const char *tmp_file;
1079 apr_array_header_t *commit_items
1080 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1082 /* Add any intermediate directories to the message */
1085 for (i = 0; i < new_dirs->nelts; i++)
1087 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1089 item = svn_client_commit_item3_create(pool);
1090 item->url = svn_path_url_add_component2(top_url, relpath, pool);
1091 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1092 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1096 for (i = 0; i < path_infos->nelts; i++)
1098 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1099 path_driver_info_t *);
1101 item = svn_client_commit_item3_create(pool);
1102 item->url = svn_path_url_add_component2(top_url, info->dst_path,
1104 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1105 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1107 if (is_move && (! info->resurrection))
1109 item = apr_pcalloc(pool, sizeof(*item));
1110 item->url = svn_path_url_add_component2(top_url, info->src_path,
1112 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1113 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1117 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1120 return SVN_NO_ERROR;
1125 /* Setup our PATHS for the path-based editor drive. */
1126 /* First any intermediate directories. */
1129 for (i = 0; i < new_dirs->nelts; i++)
1131 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1132 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1134 info->dst_path = relpath;
1135 info->dir_add = TRUE;
1137 APR_ARRAY_PUSH(paths, const char *) = relpath;
1138 svn_hash_sets(action_hash, relpath, info);
1142 /* Then our copy destinations and move sources (if any). */
1143 for (i = 0; i < path_infos->nelts; i++)
1145 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1146 path_driver_info_t *);
1148 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1149 if (is_move && (! info->resurrection))
1150 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1153 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1154 message, ctx, pool));
1156 /* Fetch RA commit editor. */
1157 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1158 svn_client__get_shim_callbacks(ctx->wc_ctx,
1160 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1164 NULL, TRUE, /* No lock tokens */
1167 /* Setup the callback baton. */
1168 cb_baton.editor = editor;
1169 cb_baton.edit_baton = edit_baton;
1170 cb_baton.action_hash = action_hash;
1171 cb_baton.is_move = is_move;
1173 /* Call the path-based editor driver. */
1174 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1175 path_driver_cb_func, &cb_baton, pool);
1178 /* At least try to abort the edit (and fs txn) before throwing err. */
1179 return svn_error_compose_create(
1181 editor->abort_edit(edit_baton, pool));
1184 /* Close the edit. */
1185 return svn_error_trace(editor->close_edit(edit_baton, pool));
1188 /* Baton for check_url_kind */
1189 struct check_url_kind_baton
1191 svn_ra_session_t *session;
1192 const char *repos_root_url;
1193 svn_boolean_t should_reparent;
1196 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1197 static svn_error_t *
1198 check_url_kind(void *baton,
1199 svn_node_kind_t *kind,
1201 svn_revnum_t revision,
1202 apr_pool_t *scratch_pool)
1204 struct check_url_kind_baton *cukb = baton;
1206 /* If we don't have a session or can't use the session, get one */
1207 if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1208 *kind = svn_node_none;
1211 cukb->should_reparent = TRUE;
1213 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1215 SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1216 kind, scratch_pool));
1219 return SVN_NO_ERROR;
1223 * COMMIT_INFO_P is ...
1224 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1225 * and each 'dst_abspath_or_url' is a URL.
1226 * MAKE_PARENTS is ...
1227 * REVPROP_TABLE is ...
1229 static svn_error_t *
1230 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1231 svn_boolean_t make_parents,
1232 const apr_hash_t *revprop_table,
1233 svn_commit_callback2_t commit_callback,
1235 svn_client_ctx_t *ctx,
1236 apr_pool_t *scratch_pool)
1238 const char *message;
1239 const char *top_src_path, *top_dst_url;
1240 struct check_url_kind_baton cukb;
1241 const char *top_src_abspath;
1242 svn_ra_session_t *ra_session;
1243 const svn_delta_editor_t *editor;
1244 apr_hash_t *relpath_map = NULL;
1246 svn_client__committables_t *committables;
1247 apr_array_header_t *commit_items;
1248 apr_pool_t *iterpool;
1249 apr_array_header_t *new_dirs = NULL;
1250 apr_hash_t *commit_revprops;
1251 svn_client__copy_pair_t *first_pair;
1252 apr_pool_t *session_pool = svn_pool_create(scratch_pool);
1255 /* Find the common root of all the source paths */
1256 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
1259 /* Do we need to lock the working copy? 1.6 didn't take a write
1260 lock, but what happens if the working copy changes during the copy
1263 iterpool = svn_pool_create(scratch_pool);
1265 /* Determine the longest common ancestor for the destinations, and open an RA
1266 session to that location. */
1267 /* ### But why start by getting the _parent_ of the first one? */
1268 /* --- That works because multiple destinations always point to the same
1269 * directory. I'm rather wondering why we need to find a common
1270 * destination parent here at all, instead of simply getting
1271 * top_dst_url from get_copy_pair_ancestors() above?
1272 * It looks like the entire block of code hanging off this comment
1274 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1275 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
1276 for (i = 1; i < copy_pairs->nelts; i++)
1278 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1279 svn_client__copy_pair_t *);
1280 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
1281 pair->dst_abspath_or_url,
1285 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
1287 /* Open a session to help while determining the exact targets */
1288 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1289 top_src_abspath, NULL,
1290 FALSE /* write_dav_props */,
1291 TRUE /* read_dav_props */,
1293 session_pool, session_pool));
1295 /* If requested, determine the nearest existing parent of the destination,
1296 and reparent the ra session there. */
1299 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
1300 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
1304 /* Figure out the basename that will result from each copy and check to make
1305 sure it doesn't exist already. */
1306 for (i = 0; i < copy_pairs->nelts; i++)
1308 svn_node_kind_t dst_kind;
1309 const char *dst_rel;
1310 svn_client__copy_pair_t *pair =
1311 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1313 svn_pool_clear(iterpool);
1314 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
1316 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1317 &dst_kind, iterpool));
1318 if (dst_kind != svn_node_none)
1320 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1321 _("Path '%s' already exists"),
1322 pair->dst_abspath_or_url);
1326 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1328 /* Produce a list of new paths to add, and provide it to the
1329 mechanism used to acquire a log message. */
1330 svn_client_commit_item3_t *item;
1331 const char *tmp_file;
1332 commit_items = apr_array_make(scratch_pool, copy_pairs->nelts,
1335 /* Add any intermediate directories to the message */
1338 for (i = 0; i < new_dirs->nelts; i++)
1340 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1342 item = svn_client_commit_item3_create(scratch_pool);
1344 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1345 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1349 for (i = 0; i < copy_pairs->nelts; i++)
1351 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1352 svn_client__copy_pair_t *);
1354 item = svn_client_commit_item3_create(scratch_pool);
1355 item->url = pair->dst_abspath_or_url;
1356 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1357 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1360 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1361 ctx, scratch_pool));
1364 svn_pool_destroy(iterpool);
1365 svn_pool_destroy(session_pool);
1366 return SVN_NO_ERROR;
1372 cukb.session = ra_session;
1373 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
1374 cukb.should_reparent = FALSE;
1376 /* Crawl the working copy for commit items. */
1377 /* ### TODO: Pass check_url_func for issue #3314 handling */
1378 SVN_ERR(svn_client__get_copy_committables(&committables,
1380 check_url_kind, &cukb,
1381 ctx, scratch_pool, iterpool));
1383 /* The committables are keyed by the repository root */
1384 commit_items = svn_hash_gets(committables->by_repository,
1385 cukb.repos_root_url);
1386 SVN_ERR_ASSERT(commit_items != NULL);
1388 if (cukb.should_reparent)
1389 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
1391 /* If we are creating intermediate directories, tack them onto the list
1395 for (i = 0; i < new_dirs->nelts; i++)
1397 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1398 svn_client_commit_item3_t *item;
1400 item = svn_client_commit_item3_create(scratch_pool);
1402 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1403 item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
1404 sizeof(svn_prop_t *));
1405 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1409 /* ### TODO: This extra loop would be unnecessary if this code lived
1410 ### in svn_client__get_copy_committables(), which is incidentally
1411 ### only used above (so should really be in this source file). */
1412 for (i = 0; i < copy_pairs->nelts; i++)
1414 apr_hash_t *mergeinfo, *wc_mergeinfo;
1415 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1416 svn_client__copy_pair_t *);
1417 svn_client_commit_item3_t *item =
1418 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1419 svn_client__pathrev_t *src_origin;
1421 svn_pool_clear(iterpool);
1423 SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
1424 pair->src_abspath_or_url,
1425 ctx, iterpool, iterpool));
1427 /* Set the mergeinfo for the destination to the combined merge
1428 info known to the WC and the repository. */
1429 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
1430 sizeof(svn_prop_t *));
1431 /* Repository mergeinfo (or NULL if it's locally added)... */
1433 SVN_ERR(svn_client__get_repos_mergeinfo(
1434 &mergeinfo, ra_session, src_origin->url, src_origin->rev,
1435 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
1438 /* ... and WC mergeinfo. */
1439 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
1440 pair->src_abspath_or_url,
1441 iterpool, iterpool));
1442 if (wc_mergeinfo && mergeinfo)
1443 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
1445 else if (! mergeinfo)
1446 mergeinfo = wc_mergeinfo;
1449 /* Push a mergeinfo prop representing MERGEINFO onto the
1450 * OUTGOING_PROP_CHANGES array. */
1452 svn_prop_t *mergeinfo_prop
1453 = apr_palloc(item->outgoing_prop_changes->pool,
1454 sizeof(svn_prop_t));
1455 svn_string_t *prop_value;
1457 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
1458 item->outgoing_prop_changes->pool));
1460 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
1461 mergeinfo_prop->value = prop_value;
1462 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
1467 /* Sort and condense our COMMIT_ITEMS. */
1468 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
1469 commit_items, scratch_pool));
1471 #ifdef ENABLE_EV2_SHIMS
1474 relpath_map = apr_hash_make(pool);
1475 for (i = 0; i < commit_items->nelts; i++)
1477 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
1478 svn_client_commit_item3_t *);
1479 const char *relpath;
1484 svn_pool_clear(iterpool);
1485 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
1486 ctx->wc_ctx, item->path, FALSE,
1487 scratch_pool, iterpool));
1489 svn_hash_sets(relpath_map, relpath, item->path);
1494 /* Close the initial session, to reopen a new session with commit handling */
1495 svn_pool_clear(session_pool);
1497 /* Open a new RA session to DST_URL. */
1498 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1501 session_pool, session_pool));
1503 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1504 message, ctx, session_pool));
1506 /* Fetch RA commit editor. */
1507 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1508 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
1510 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1514 TRUE, /* No lock tokens */
1517 /* Perform the commit. */
1518 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
1520 0, /* ### any notify_path_offset needed? */
1521 NULL, ctx, session_pool, session_pool),
1522 _("Commit failed (details follow):"));
1524 svn_pool_destroy(iterpool);
1525 svn_pool_destroy(session_pool);
1527 return SVN_NO_ERROR;
1530 /* A baton for notification_adjust_func(). */
1531 struct notification_adjust_baton
1533 svn_wc_notify_func2_t inner_func;
1535 const char *checkout_abspath;
1536 const char *final_abspath;
1539 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
1540 * baton is BATON->inner_baton) and adjusts the notification paths that
1541 * start with BATON->checkout_abspath to start instead with
1542 * BATON->final_abspath. */
1544 notification_adjust_func(void *baton,
1545 const svn_wc_notify_t *notify,
1548 struct notification_adjust_baton *nb = baton;
1549 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
1550 const char *relpath;
1552 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
1553 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
1556 nb->inner_func(nb->inner_baton, inner_notify, pool);
1559 /* Peform each individual copy operation for a repos -> wc copy. A
1560 helper for repos_to_wc_copy().
1562 Resolve PAIR->src_revnum to a real revision number if it isn't already. */
1563 static svn_error_t *
1564 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
1565 svn_client__copy_pair_t *pair,
1566 svn_boolean_t same_repositories,
1567 svn_boolean_t ignore_externals,
1568 svn_ra_session_t *ra_session,
1569 svn_client_ctx_t *ctx,
1572 apr_hash_t *src_mergeinfo;
1573 const char *dst_abspath = pair->dst_abspath_or_url;
1575 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
1577 if (!same_repositories && ctx->notify_func2)
1579 svn_wc_notify_t *notify;
1580 notify = svn_wc_create_notify_url(
1581 pair->src_abspath_or_url,
1582 svn_wc_notify_foreign_copy_begin,
1584 notify->kind = pair->src_kind;
1585 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1587 /* Allow a theoretical cancel to get through. */
1588 if (ctx->cancel_func)
1589 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1592 if (pair->src_kind == svn_node_dir)
1594 if (same_repositories)
1596 svn_boolean_t sleep_needed = FALSE;
1597 const char *tmpdir_abspath, *tmp_abspath;
1599 /* Find a temporary location in which to check out the copy source. */
1600 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
1603 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
1604 svn_io_file_del_on_close, pool, pool));
1606 /* Make a new checkout of the requested source. While doing so,
1607 * resolve pair->src_revnum to an actual revision number in case it
1608 * was until now 'invalid' meaning 'head'. Ask this function not to
1609 * sleep for timestamps, by passing a sleep_needed output param.
1610 * Send notifications for all nodes except the root node, and adjust
1611 * them to refer to the destination rather than this temporary path. */
1613 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
1614 void *old_notify_baton2 = ctx->notify_baton2;
1615 struct notification_adjust_baton nb;
1618 nb.inner_func = ctx->notify_func2;
1619 nb.inner_baton = ctx->notify_baton2;
1620 nb.checkout_abspath = tmp_abspath;
1621 nb.final_abspath = dst_abspath;
1622 ctx->notify_func2 = notification_adjust_func;
1623 ctx->notify_baton2 = &nb;
1625 err = svn_client__checkout_internal(&pair->src_revnum,
1628 &pair->src_peg_revision,
1629 &pair->src_op_revision,
1631 ignore_externals, FALSE,
1632 &sleep_needed, ctx, pool);
1634 ctx->notify_func2 = old_notify_func2;
1635 ctx->notify_baton2 = old_notify_baton2;
1640 *timestamp_sleep = TRUE;
1642 /* Schedule dst_path for addition in parent, with copy history.
1643 Don't send any notification here.
1644 Then remove the temporary checkout's .svn dir in preparation for
1645 moving the rest of it into the final destination. */
1646 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
1647 TRUE /* metadata_only */,
1648 ctx->cancel_func, ctx->cancel_baton,
1650 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
1651 FALSE, pool, pool));
1652 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
1659 /* Move the temporary disk tree into place. */
1660 SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
1664 *timestamp_sleep = TRUE;
1666 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
1668 &pair->src_peg_revision,
1669 &pair->src_op_revision,
1671 FALSE /* make_parents */,
1672 TRUE /* already_locked */,
1675 return SVN_NO_ERROR;
1677 } /* end directory case */
1679 else if (pair->src_kind == svn_node_file)
1681 apr_hash_t *new_props;
1682 const char *src_rel;
1683 svn_stream_t *new_base_contents = svn_stream_buffered(pool);
1685 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1686 pair->src_abspath_or_url,
1688 /* Fetch the file content. While doing so, resolve pair->src_revnum
1689 * to an actual revision number if it's 'invalid' meaning 'head'. */
1690 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
1692 &pair->src_revnum, &new_props, pool));
1694 if (new_props && ! same_repositories)
1695 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
1697 *timestamp_sleep = TRUE;
1699 SVN_ERR(svn_wc_add_repos_file4(
1700 ctx->wc_ctx, dst_abspath,
1701 new_base_contents, NULL, new_props, NULL,
1702 same_repositories ? pair->src_abspath_or_url : NULL,
1703 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
1704 ctx->cancel_func, ctx->cancel_baton,
1708 /* Record the implied mergeinfo (before the notification callback
1709 is invoked for the root node). */
1710 SVN_ERR(svn_client__get_repos_mergeinfo(
1711 &src_mergeinfo, ra_session,
1712 pair->src_abspath_or_url, pair->src_revnum,
1713 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1714 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
1716 /* Do our own notification for the root node, even if we could possibly
1717 have delegated it. See also issue #1552.
1719 ### Maybe this notification should mention the mergeinfo change. */
1720 if (ctx->notify_func2)
1722 svn_wc_notify_t *notify = svn_wc_create_notify(
1723 dst_abspath, svn_wc_notify_add, pool);
1724 notify->kind = pair->src_kind;
1725 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1728 return SVN_NO_ERROR;
1731 static svn_error_t *
1732 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
1733 const apr_array_header_t *copy_pairs,
1734 const char *top_dst_path,
1735 svn_boolean_t ignore_externals,
1736 svn_ra_session_t *ra_session,
1737 svn_client_ctx_t *ctx,
1738 apr_pool_t *scratch_pool)
1741 svn_boolean_t same_repositories;
1742 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1744 /* We've already checked for physical obstruction by a working file.
1745 But there could also be logical obstruction by an entry whose
1746 working file happens to be missing.*/
1747 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
1748 ctx, scratch_pool, iterpool));
1750 /* Decide whether the two repositories are the same or not. */
1752 svn_error_t *src_err, *dst_err;
1754 const char *parent_abspath;
1755 const char *src_uuid, *dst_uuid;
1757 /* Get the repository uuid of SRC_URL */
1758 src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
1759 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1760 return svn_error_trace(src_err);
1762 /* Get repository uuid of dst's parent directory, since dst may
1763 not exist. ### TODO: we should probably walk up the wc here,
1764 in case the parent dir has an imaginary URL. */
1765 if (copy_pairs->nelts == 1)
1766 parent = svn_dirent_dirname(top_dst_path, scratch_pool);
1768 parent = top_dst_path;
1770 SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
1771 dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
1772 parent_abspath, ctx,
1773 iterpool, iterpool);
1774 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1777 /* If either of the UUIDs are nonexistent, then at least one of
1778 the repositories must be very old. Rather than punish the
1779 user, just assume the repositories are different, so no
1780 copy-history is attempted. */
1781 if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
1782 same_repositories = FALSE;
1784 same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
1787 /* Perform the move for each of the copy_pairs. */
1788 for (i = 0; i < copy_pairs->nelts; i++)
1790 /* Check for cancellation */
1791 if (ctx->cancel_func)
1792 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1794 svn_pool_clear(iterpool);
1796 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
1797 APR_ARRAY_IDX(copy_pairs, i,
1798 svn_client__copy_pair_t *),
1801 ra_session, ctx, iterpool));
1803 svn_pool_destroy(iterpool);
1805 return SVN_NO_ERROR;
1808 static svn_error_t *
1809 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
1810 const apr_array_header_t *copy_pairs,
1811 svn_boolean_t make_parents,
1812 svn_boolean_t ignore_externals,
1813 svn_client_ctx_t *ctx,
1816 svn_ra_session_t *ra_session;
1817 const char *top_src_url, *top_dst_path;
1818 apr_pool_t *iterpool = svn_pool_create(pool);
1819 const char *lock_abspath;
1822 /* Get the real path for the source, based upon its peg revision. */
1823 for (i = 0; i < copy_pairs->nelts; i++)
1825 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1826 svn_client__copy_pair_t *);
1829 svn_pool_clear(iterpool);
1831 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
1833 pair->src_abspath_or_url,
1834 &pair->src_peg_revision,
1835 &pair->src_op_revision, NULL,
1838 pair->src_original = pair->src_abspath_or_url;
1839 pair->src_abspath_or_url = apr_pstrdup(pool, src);
1842 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
1844 lock_abspath = top_dst_path;
1845 if (copy_pairs->nelts == 1)
1847 top_src_url = svn_uri_dirname(top_src_url, pool);
1848 lock_abspath = svn_dirent_dirname(top_dst_path, pool);
1851 /* Open a repository session to the longest common src ancestor. We do not
1852 (yet) have a working copy, so we don't have a corresponding path and
1853 tempfiles cannot go into the admin area. */
1854 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
1857 /* Get the correct src path for the peg revision used, and verify that we
1858 aren't overwriting an existing path. */
1859 for (i = 0; i < copy_pairs->nelts; i++)
1861 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1862 svn_client__copy_pair_t *);
1863 svn_node_kind_t dst_parent_kind, dst_kind;
1864 const char *dst_parent;
1865 const char *src_rel;
1867 svn_pool_clear(iterpool);
1869 /* Next, make sure that the path exists in the repository. */
1870 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1871 pair->src_abspath_or_url,
1873 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1874 &pair->src_kind, pool));
1875 if (pair->src_kind == svn_node_none)
1877 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
1878 return svn_error_createf
1879 (SVN_ERR_FS_NOT_FOUND, NULL,
1880 _("Path '%s' not found in revision %ld"),
1881 pair->src_abspath_or_url, pair->src_revnum);
1883 return svn_error_createf
1884 (SVN_ERR_FS_NOT_FOUND, NULL,
1885 _("Path '%s' not found in head revision"),
1886 pair->src_abspath_or_url);
1889 /* Figure out about dst. */
1890 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1892 if (dst_kind != svn_node_none)
1894 return svn_error_createf(
1895 SVN_ERR_ENTRY_EXISTS, NULL,
1896 _("Path '%s' already exists"),
1897 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
1900 /* Make sure the destination parent is a directory and produce a clear
1901 error message if it is not. */
1902 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
1903 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
1904 if (make_parents && dst_parent_kind == svn_node_none)
1906 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
1909 else if (dst_parent_kind != svn_node_dir)
1911 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1912 _("Path '%s' is not a directory"),
1913 svn_dirent_local_style(dst_parent, pool));
1916 svn_pool_destroy(iterpool);
1918 SVN_WC__CALL_WITH_WRITE_LOCK(
1919 repos_to_wc_copy_locked(timestamp_sleep,
1920 copy_pairs, top_dst_path, ignore_externals,
1921 ra_session, ctx, pool),
1922 ctx->wc_ctx, lock_abspath, FALSE, pool);
1923 return SVN_NO_ERROR;
1926 #define NEED_REPOS_REVNUM(revision) \
1927 ((revision.kind != svn_opt_revision_unspecified) \
1928 && (revision.kind != svn_opt_revision_working))
1932 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
1933 * change *TIMESTAMP_SLEEP. This output will be valid even if the
1934 * function returns an error.
1936 * Perform all allocations in POOL.
1938 static svn_error_t *
1939 try_copy(svn_boolean_t *timestamp_sleep,
1940 const apr_array_header_t *sources,
1941 const char *dst_path_in,
1942 svn_boolean_t is_move,
1943 svn_boolean_t allow_mixed_revisions,
1944 svn_boolean_t metadata_only,
1945 svn_boolean_t make_parents,
1946 svn_boolean_t ignore_externals,
1947 const apr_hash_t *revprop_table,
1948 svn_commit_callback2_t commit_callback,
1950 svn_client_ctx_t *ctx,
1953 apr_array_header_t *copy_pairs =
1954 apr_array_make(pool, sources->nelts,
1955 sizeof(svn_client__copy_pair_t *));
1956 svn_boolean_t srcs_are_urls, dst_is_url;
1959 /* Are either of our paths URLs? Just check the first src_path. If
1960 there are more than one, we'll check for homogeneity among them
1962 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
1963 svn_client_copy_source_t *)->path);
1964 dst_is_url = svn_path_is_url(dst_path_in);
1966 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
1968 /* If we have multiple source paths, it implies the dst_path is a
1969 directory we are moving or copying into. Populate the COPY_PAIRS
1970 array to contain a destination path for each of the source paths. */
1971 if (sources->nelts > 1)
1973 apr_pool_t *iterpool = svn_pool_create(pool);
1975 for (i = 0; i < sources->nelts; i++)
1977 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
1978 svn_client_copy_source_t *);
1979 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1980 const char *src_basename;
1981 svn_boolean_t src_is_url = svn_path_is_url(source->path);
1983 svn_pool_clear(iterpool);
1987 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
1988 src_basename = svn_uri_basename(pair->src_abspath_or_url,
1993 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
1994 source->path, pool));
1995 src_basename = svn_dirent_basename(pair->src_abspath_or_url,
1999 pair->src_op_revision = *source->revision;
2000 pair->src_peg_revision = *source->peg_revision;
2002 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2003 &pair->src_op_revision,
2008 /* Check to see if all the sources are urls or all working copy
2010 if (src_is_url != srcs_are_urls)
2011 return svn_error_create
2012 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2013 _("Cannot mix repository and working copy sources"));
2016 pair->dst_abspath_or_url =
2017 svn_path_url_add_component2(dst_path_in, src_basename, pool);
2019 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2020 src_basename, pool);
2021 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2024 svn_pool_destroy(iterpool);
2028 /* Only one source path. */
2029 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
2030 svn_client_copy_source_t *source =
2031 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2032 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2035 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2037 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2038 source->path, pool));
2039 pair->src_op_revision = *source->revision;
2040 pair->src_peg_revision = *source->peg_revision;
2042 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2043 &pair->src_op_revision,
2044 src_is_url, TRUE, pool));
2046 pair->dst_abspath_or_url = dst_path_in;
2047 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2050 if (!srcs_are_urls && !dst_is_url)
2052 apr_pool_t *iterpool = svn_pool_create(pool);
2054 for (i = 0; i < copy_pairs->nelts; i++)
2056 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2057 svn_client__copy_pair_t *);
2059 svn_pool_clear(iterpool);
2061 if (svn_dirent_is_child(pair->src_abspath_or_url,
2062 pair->dst_abspath_or_url, iterpool))
2063 return svn_error_createf
2064 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2065 _("Cannot copy path '%s' into its own child '%s'"),
2066 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2067 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2070 svn_pool_destroy(iterpool);
2073 /* A file external should not be moved since the file external is
2074 implemented as a switched file and it would delete the file the
2075 file external is switched to, which is not the behavior the user
2076 would probably want. */
2077 if (is_move && !srcs_are_urls)
2079 apr_pool_t *iterpool = svn_pool_create(pool);
2081 for (i = 0; i < copy_pairs->nelts; i++)
2083 svn_client__copy_pair_t *pair =
2084 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2085 svn_node_kind_t external_kind;
2086 const char *defining_abspath;
2088 svn_pool_clear(iterpool);
2090 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2091 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2092 NULL, NULL, NULL, ctx->wc_ctx,
2093 pair->src_abspath_or_url,
2094 pair->src_abspath_or_url, TRUE,
2095 iterpool, iterpool));
2097 if (external_kind != svn_node_none)
2098 return svn_error_createf(
2099 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2101 _("Cannot move the external at '%s'; please "
2102 "edit the svn:externals property on '%s'."),
2103 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2104 svn_dirent_local_style(defining_abspath, pool));
2106 svn_pool_destroy(iterpool);
2111 /* Disallow moves between the working copy and the repository. */
2112 if (srcs_are_urls != dst_is_url)
2114 return svn_error_create
2115 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2116 _("Moves between the working copy and the repository are not "
2120 /* Disallow moving any path/URL onto or into itself. */
2121 for (i = 0; i < copy_pairs->nelts; i++)
2123 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2124 svn_client__copy_pair_t *);
2126 if (strcmp(pair->src_abspath_or_url,
2127 pair->dst_abspath_or_url) == 0)
2128 return svn_error_createf(
2129 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2131 _("Cannot move URL '%s' into itself") :
2132 _("Cannot move path '%s' into itself"),
2134 pair->src_abspath_or_url :
2135 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2142 /* If we are doing a wc->* copy, but with an operational revision
2143 other than the working copy revision, we are really doing a
2144 repo->* copy, because we're going to need to get the rev from the
2147 svn_boolean_t need_repos_op_rev = FALSE;
2148 svn_boolean_t need_repos_peg_rev = FALSE;
2150 /* Check to see if any revision is something other than
2151 svn_opt_revision_unspecified or svn_opt_revision_working. */
2152 for (i = 0; i < copy_pairs->nelts; i++)
2154 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2155 svn_client__copy_pair_t *);
2157 if (NEED_REPOS_REVNUM(pair->src_op_revision))
2158 need_repos_op_rev = TRUE;
2160 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
2161 need_repos_peg_rev = TRUE;
2163 if (need_repos_op_rev || need_repos_peg_rev)
2167 if (need_repos_op_rev || need_repos_peg_rev)
2169 apr_pool_t *iterpool = svn_pool_create(pool);
2171 for (i = 0; i < copy_pairs->nelts; i++)
2173 const char *copyfrom_repos_root_url;
2174 const char *copyfrom_repos_relpath;
2176 svn_revnum_t copyfrom_rev;
2177 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2178 svn_client__copy_pair_t *);
2180 svn_pool_clear(iterpool);
2182 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2184 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev,
2185 ©from_repos_relpath,
2186 ©from_repos_root_url,
2189 pair->src_abspath_or_url,
2190 TRUE, iterpool, iterpool));
2192 if (copyfrom_repos_relpath)
2193 url = svn_path_url_add_component2(copyfrom_repos_root_url,
2194 copyfrom_repos_relpath,
2197 return svn_error_createf
2198 (SVN_ERR_ENTRY_MISSING_URL, NULL,
2199 _("'%s' does not have a URL associated with it"),
2200 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2202 pair->src_abspath_or_url = url;
2204 if (!need_repos_peg_rev
2205 || pair->src_peg_revision.kind == svn_opt_revision_base)
2207 /* Default the peg revision to that of the WC entry. */
2208 pair->src_peg_revision.kind = svn_opt_revision_number;
2209 pair->src_peg_revision.value.number = copyfrom_rev;
2212 if (pair->src_op_revision.kind == svn_opt_revision_base)
2214 /* Use the entry's revision as the operational rev. */
2215 pair->src_op_revision.kind = svn_opt_revision_number;
2216 pair->src_op_revision.value.number = copyfrom_rev;
2220 svn_pool_destroy(iterpool);
2221 srcs_are_urls = TRUE;
2226 /* Now, call the right handler for the operation. */
2227 if ((! srcs_are_urls) && (! dst_is_url))
2229 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
2230 metadata_only, ctx, pool, pool));
2232 /* Copy or move all targets. */
2234 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
2235 copy_pairs, dst_path_in,
2236 allow_mixed_revisions,
2241 /* We ignore these values, so assert the default value */
2242 SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
2243 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
2244 copy_pairs, ctx, pool));
2247 else if ((! srcs_are_urls) && (dst_is_url))
2249 return svn_error_trace(
2250 wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
2251 commit_callback, commit_baton, ctx, pool));
2253 else if ((srcs_are_urls) && (! dst_is_url))
2255 return svn_error_trace(
2256 repos_to_wc_copy(timestamp_sleep,
2257 copy_pairs, make_parents, ignore_externals,
2262 return svn_error_trace(
2263 repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
2264 commit_callback, commit_baton, ctx, is_move,
2271 /* Public Interfaces */
2273 svn_client_copy6(const apr_array_header_t *sources,
2274 const char *dst_path,
2275 svn_boolean_t copy_as_child,
2276 svn_boolean_t make_parents,
2277 svn_boolean_t ignore_externals,
2278 const apr_hash_t *revprop_table,
2279 svn_commit_callback2_t commit_callback,
2281 svn_client_ctx_t *ctx,
2285 svn_boolean_t timestamp_sleep = FALSE;
2286 apr_pool_t *subpool = svn_pool_create(pool);
2288 if (sources->nelts > 1 && !copy_as_child)
2289 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2292 err = try_copy(×tamp_sleep,
2294 FALSE /* is_move */,
2295 TRUE /* allow_mixed_revisions */,
2296 FALSE /* metadata_only */,
2300 commit_callback, commit_baton,
2304 /* If the destination exists, try to copy the sources as children of the
2306 if (copy_as_child && err && (sources->nelts == 1)
2307 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2308 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2310 const char *src_path = APR_ARRAY_IDX(sources, 0,
2311 svn_client_copy_source_t *)->path;
2312 const char *src_basename;
2313 svn_boolean_t src_is_url = svn_path_is_url(src_path);
2314 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2316 svn_error_clear(err);
2317 svn_pool_clear(subpool);
2319 src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
2320 : svn_dirent_basename(src_path, subpool);
2322 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2324 : svn_dirent_join(dst_path, src_basename, subpool);
2326 err = try_copy(×tamp_sleep,
2328 FALSE /* is_move */,
2329 TRUE /* allow_mixed_revisions */,
2330 FALSE /* metadata_only */,
2334 commit_callback, commit_baton,
2339 /* Sleep if required. DST_PATH is not a URL in these cases. */
2340 if (timestamp_sleep)
2341 svn_io_sleep_for_timestamps(dst_path, subpool);
2343 svn_pool_destroy(subpool);
2344 return svn_error_trace(err);
2349 svn_client_move7(const apr_array_header_t *src_paths,
2350 const char *dst_path,
2351 svn_boolean_t move_as_child,
2352 svn_boolean_t make_parents,
2353 svn_boolean_t allow_mixed_revisions,
2354 svn_boolean_t metadata_only,
2355 const apr_hash_t *revprop_table,
2356 svn_commit_callback2_t commit_callback,
2358 svn_client_ctx_t *ctx,
2361 const svn_opt_revision_t head_revision
2362 = { svn_opt_revision_head, { 0 } };
2364 svn_boolean_t timestamp_sleep = FALSE;
2366 apr_pool_t *subpool = svn_pool_create(pool);
2367 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
2368 sizeof(const svn_client_copy_source_t *));
2370 if (src_paths->nelts > 1 && !move_as_child)
2371 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2374 for (i = 0; i < src_paths->nelts; i++)
2376 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
2377 svn_client_copy_source_t *copy_source = apr_palloc(pool,
2378 sizeof(*copy_source));
2380 copy_source->path = src_path;
2381 copy_source->revision = &head_revision;
2382 copy_source->peg_revision = &head_revision;
2384 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
2387 err = try_copy(×tamp_sleep,
2390 allow_mixed_revisions,
2393 FALSE /* ignore_externals */,
2395 commit_callback, commit_baton,
2399 /* If the destination exists, try to move the sources as children of the
2401 if (move_as_child && err && (src_paths->nelts == 1)
2402 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2403 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2405 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
2406 const char *src_basename;
2407 svn_boolean_t src_is_url = svn_path_is_url(src_path);
2408 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2410 svn_error_clear(err);
2411 svn_pool_clear(subpool);
2413 src_basename = src_is_url ? svn_uri_basename(src_path, pool)
2414 : svn_dirent_basename(src_path, pool);
2416 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2418 : svn_dirent_join(dst_path, src_basename, subpool);
2420 err = try_copy(×tamp_sleep,
2423 allow_mixed_revisions,
2426 FALSE /* ignore_externals */,
2428 commit_callback, commit_baton,
2433 /* Sleep if required. DST_PATH is not a URL in these cases. */
2434 if (timestamp_sleep)
2435 svn_io_sleep_for_timestamps(dst_path, subpool);
2437 svn_pool_destroy(subpool);
2438 return svn_error_trace(err);