copy.c revision 289166
1/* 2 * copy.c: copy/move wrappers around wc 'copy' functionality. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <string.h> 31#include "svn_hash.h" 32#include "svn_client.h" 33#include "svn_error.h" 34#include "svn_error_codes.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_opt.h" 38#include "svn_time.h" 39#include "svn_props.h" 40#include "svn_mergeinfo.h" 41#include "svn_pools.h" 42 43#include "client.h" 44#include "mergeinfo.h" 45 46#include "svn_private_config.h" 47#include "private/svn_wc_private.h" 48#include "private/svn_ra_private.h" 49#include "private/svn_mergeinfo_private.h" 50#include "private/svn_client_private.h" 51 52 53/* 54 * OUR BASIC APPROACH TO COPIES 55 * ============================ 56 * 57 * for each source/destination pair 58 * if (not exist src_path) 59 * return ERR_BAD_SRC error 60 * 61 * if (exist dst_path) 62 * return ERR_OBSTRUCTION error 63 * else 64 * copy src_path into parent_of_dst_path as basename (dst_path) 65 * 66 * if (this is a move) 67 * delete src_path 68 */ 69 70 71 72/*** Code. ***/ 73 74/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding 75 MERGEINFO to any mergeinfo pre-existing in the WC. */ 76static svn_error_t * 77extend_wc_mergeinfo(const char *target_abspath, 78 apr_hash_t *mergeinfo, 79 svn_client_ctx_t *ctx, 80 apr_pool_t *pool) 81{ 82 apr_hash_t *wc_mergeinfo; 83 84 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo 85 updating it. */ 86 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 87 target_abspath, pool, pool)); 88 89 /* Combine the provided mergeinfo with any mergeinfo from the WC. */ 90 if (wc_mergeinfo && mergeinfo) 91 SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool)); 92 else if (! wc_mergeinfo) 93 wc_mergeinfo = mergeinfo; 94 95 return svn_error_trace( 96 svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo, 97 FALSE, ctx, pool)); 98} 99 100/* Find the longest common ancestor of paths in COPY_PAIRS. If 101 SRC_ANCESTOR is NULL, ignore source paths in this calculation. If 102 DST_ANCESTOR is NULL, ignore destination paths in this calculation. 103 COMMON_ANCESTOR will be the common ancestor of both the 104 SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not 105 NULL. 106 */ 107static svn_error_t * 108get_copy_pair_ancestors(const apr_array_header_t *copy_pairs, 109 const char **src_ancestor, 110 const char **dst_ancestor, 111 const char **common_ancestor, 112 apr_pool_t *pool) 113{ 114 apr_pool_t *subpool = svn_pool_create(pool); 115 svn_client__copy_pair_t *first; 116 const char *first_dst; 117 const char *first_src; 118 const char *top_dst; 119 svn_boolean_t src_is_url; 120 svn_boolean_t dst_is_url; 121 char *top_src; 122 int i; 123 124 first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 125 126 /* Because all the destinations are in the same directory, we can easily 127 determine their common ancestor. */ 128 first_dst = first->dst_abspath_or_url; 129 dst_is_url = svn_path_is_url(first_dst); 130 131 if (copy_pairs->nelts == 1) 132 top_dst = apr_pstrdup(subpool, first_dst); 133 else 134 top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool) 135 : svn_dirent_dirname(first_dst, subpool); 136 137 /* Sources can came from anywhere, so we have to actually do some 138 work for them. */ 139 first_src = first->src_abspath_or_url; 140 src_is_url = svn_path_is_url(first_src); 141 top_src = apr_pstrdup(subpool, first_src); 142 for (i = 1; i < copy_pairs->nelts; i++) 143 { 144 /* We don't need to clear the subpool here for several reasons: 145 1) If we do, we can't use it to allocate the initial versions of 146 top_src and top_dst (above). 147 2) We don't return any errors in the following loop, so we 148 are guanteed to destroy the subpool at the end of this function. 149 3) The number of iterations is likely to be few, and the loop will 150 be through quickly, so memory leakage will not be significant, 151 in time or space. 152 */ 153 const svn_client__copy_pair_t *pair = 154 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 155 156 top_src = src_is_url 157 ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url, 158 subpool) 159 : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url, 160 subpool); 161 } 162 163 if (src_ancestor) 164 *src_ancestor = apr_pstrdup(pool, top_src); 165 166 if (dst_ancestor) 167 *dst_ancestor = apr_pstrdup(pool, top_dst); 168 169 if (common_ancestor) 170 *common_ancestor = 171 src_is_url 172 ? svn_uri_get_longest_ancestor(top_src, top_dst, pool) 173 : svn_dirent_get_longest_ancestor(top_src, top_dst, pool); 174 175 svn_pool_destroy(subpool); 176 177 return SVN_NO_ERROR; 178} 179 180 181/* The guts of do_wc_to_wc_copies */ 182static svn_error_t * 183do_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) 188{ 189 int i; 190 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 191 svn_error_t *err = SVN_NO_ERROR; 192 193 for (i = 0; i < copy_pairs->nelts; i++) 194 { 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); 199 200 /* Check for cancellation */ 201 if (ctx->cancel_func) 202 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 203 204 /* Perform the copy */ 205 dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name, 206 iterpool); 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); 212 if (err) 213 break; 214 } 215 svn_pool_destroy(iterpool); 216 217 SVN_ERR(err); 218 return SVN_NO_ERROR; 219} 220 221/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary 222 allocations. */ 223static svn_error_t * 224do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep, 225 const apr_array_header_t *copy_pairs, 226 svn_client_ctx_t *ctx, 227 apr_pool_t *pool) 228{ 229 const char *dst_parent, *dst_parent_abspath; 230 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); 234 235 SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool)); 236 237 SVN_WC__CALL_WITH_WRITE_LOCK( 238 do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent, 239 ctx, pool), 240 ctx->wc_ctx, dst_parent_abspath, FALSE, pool); 241 242 return SVN_NO_ERROR; 243} 244 245/* The locked bit of do_wc_to_wc_moves. */ 246static svn_error_t * 247do_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) 255{ 256 const char *dst_abspath; 257 258 dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name, 259 scratch_pool); 260 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, 266 scratch_pool)); 267 268 return SVN_NO_ERROR; 269} 270 271/* Wrapper to add an optional second lock */ 272static svn_error_t * 273do_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) 281{ 282 if (lock_dst) 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, 286 metadata_only, 287 ctx, scratch_pool), 288 ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool); 289 else 290 SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 291 lock_dst, allow_mixed_revisions, 292 metadata_only, 293 ctx, scratch_pool)); 294 295 return SVN_NO_ERROR; 296} 297 298/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC 299 afterwards. Use POOL for temporary allocations. */ 300static svn_error_t * 301do_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, 307 apr_pool_t *pool) 308{ 309 int i; 310 apr_pool_t *iterpool = svn_pool_create(pool); 311 svn_error_t *err = SVN_NO_ERROR; 312 313 for (i = 0; i < copy_pairs->nelts; i++) 314 { 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; 319 320 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 321 svn_client__copy_pair_t *); 322 svn_pool_clear(iterpool); 323 324 /* Check for cancellation */ 325 if (ctx->cancel_func) 326 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 327 328 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url, 329 iterpool); 330 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)); 337 338 /* We now need to lock the right combination of batons. 339 Four cases: 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, 347 NULL) 348 && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath, 349 NULL))) 350 { 351 lock_src = TRUE; 352 lock_dst = FALSE; 353 } 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)) 358 { 359 lock_src = FALSE; 360 lock_dst = TRUE; 361 } 362 else 363 { 364 lock_src = TRUE; 365 lock_dst = TRUE; 366 } 367 368 *timestamp_sleep = TRUE; 369 370 /* Perform the copy and then the delete. */ 371 if (lock_src) 372 SVN_WC__CALL_WITH_WRITE_LOCK( 373 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 374 lock_src, lock_dst, 375 allow_mixed_revisions, 376 metadata_only, 377 ctx, iterpool), 378 ctx->wc_ctx, src_parent_abspath, 379 FALSE, iterpool); 380 else 381 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 382 lock_src, lock_dst, 383 allow_mixed_revisions, 384 metadata_only, 385 ctx, iterpool)); 386 387 } 388 svn_pool_destroy(iterpool); 389 390 return svn_error_trace(err); 391} 392 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 */ 396static svn_error_t * 397verify_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) 404{ 405 int i; 406 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 407 408 /* Check that DST does not exist, but its parent does */ 409 for (i = 0; i < copy_pairs->nelts; i++) 410 { 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; 414 415 svn_pool_clear(iterpool); 416 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 */, 424 iterpool)); 425 if (dst_kind != svn_node_none) 426 { 427 svn_boolean_t is_excluded; 428 svn_boolean_t is_server_excluded; 429 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, 433 iterpool)); 434 435 if (is_excluded || is_server_excluded) 436 { 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)); 441 } 442 else 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, 447 scratch_pool)); 448 } 449 450 /* Check that there is no unversioned obstruction */ 451 if (metadata_only) 452 dst_kind = svn_node_none; 453 else 454 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 455 iterpool)); 456 457 if (dst_kind != svn_node_none) 458 { 459 if (is_move 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, 463 iterpool)) == 0) 464 { 465 const char *dst; 466 char *dst_apr; 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. */ 471 472 SVN_ERR(svn_path_cstring_from_utf8(&dst, 473 pair->dst_abspath_or_url, 474 scratch_pool)); 475 476 apr_err = apr_filepath_merge(&dst_apr, NULL, dst, 477 APR_FILEPATH_TRUENAME, iterpool); 478 479 if (!apr_err) 480 { 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); 484 } 485 /* else: Don't report this error; just report the normal error */ 486 487 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0) 488 { 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); 492 493 svn_pool_destroy(iterpool); 494 return SVN_NO_ERROR; 495 } 496 } 497 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, 502 scratch_pool)); 503 } 504 505 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 506 pair->dst_abspath_or_url, result_pool); 507 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, 512 FALSE, TRUE, 513 iterpool)); 514 if (make_parents && dst_parent_kind == svn_node_none) 515 { 516 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath, 517 TRUE, ctx, iterpool)); 518 } 519 else if (dst_parent_kind != svn_node_dir) 520 { 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)); 525 } 526 527 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath, 528 &dst_parent_kind, scratch_pool)); 529 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)); 535 } 536 537 svn_pool_destroy(iterpool); 538 539 return SVN_NO_ERROR; 540} 541 542static svn_error_t * 543verify_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) 550{ 551 int i; 552 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 553 554 /* Check that all of our SRCs exist. */ 555 for (i = 0; i < copy_pairs->nelts; i++) 556 { 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); 561 562 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base 563 || pair->src_op_revision.kind == svn_opt_revision_base); 564 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, 574 scratch_pool)); 575 } 576 577 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx, 578 result_pool, iterpool)); 579 580 svn_pool_destroy(iterpool); 581 582 return SVN_NO_ERROR; 583} 584 585 586/* Path-specific state used as part of path_driver_cb_baton. */ 587typedef struct path_driver_info_t 588{ 589 const char *src_url; 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; 598 599 600/* The baton used with the path_driver_cb_func() callback for a copy 601 or move operation. */ 602struct path_driver_cb_baton 603{ 604 /* The editor (and its state) used to perform the operation. */ 605 const svn_delta_editor_t *editor; 606 void *edit_baton; 607 608 /* A hash of path -> path_driver_info_t *'s. */ 609 apr_hash_t *action_hash; 610 611 /* Whether the operation is a move or copy. */ 612 svn_boolean_t is_move; 613}; 614 615static svn_error_t * 616path_driver_cb_func(void **dir_baton, 617 void *parent_baton, 618 void *callback_baton, 619 const char *path, 620 apr_pool_t *pool) 621{ 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); 625 626 /* Initialize return value. */ 627 *dir_baton = NULL; 628 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)); 633 634 /* Check to see if we need to add the path as a directory. */ 635 if (path_info->dir_add) 636 { 637 return cb_baton->editor->add_directory(path, parent_baton, NULL, 638 SVN_INVALID_REVNUM, pool, 639 dir_baton); 640 } 641 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) 645 { 646 /* If this is a move, we do nothing. Otherwise, we do the copy. */ 647 if (! cb_baton->is_move) 648 do_add = TRUE; 649 } 650 /* Not a resurrection. */ 651 else 652 { 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) 656 { 657 if (strcmp(path_info->src_path, path) == 0) 658 do_delete = TRUE; 659 else 660 do_add = TRUE; 661 } 662 /* Not a move? This must just be the copy addition. */ 663 else 664 { 665 do_add = TRUE; 666 } 667 } 668 669 if (do_delete) 670 { 671 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM, 672 parent_baton, pool)); 673 } 674 if (do_add) 675 { 676 SVN_ERR(svn_path_check_valid(path, pool)); 677 678 if (path_info->src_kind == svn_node_file) 679 { 680 void *file_baton; 681 SVN_ERR(cb_baton->editor->add_file(path, parent_baton, 682 path_info->src_url, 683 path_info->src_revnum, 684 pool, &file_baton)); 685 if (path_info->mergeinfo) 686 SVN_ERR(cb_baton->editor->change_file_prop(file_baton, 687 SVN_PROP_MERGEINFO, 688 path_info->mergeinfo, 689 pool)); 690 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool)); 691 } 692 else 693 { 694 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, 695 path_info->src_url, 696 path_info->src_revnum, 697 pool, dir_baton)); 698 if (path_info->mergeinfo) 699 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, 700 SVN_PROP_MERGEINFO, 701 path_info->mergeinfo, 702 pool)); 703 } 704 } 705 return SVN_NO_ERROR; 706} 707 708 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. 713 714 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 715 ### implementation susceptible to race conditions. */ 716static svn_error_t * 717find_absent_parents1(svn_ra_session_t *ra_session, 718 const char *dir, 719 apr_array_header_t *new_dirs, 720 apr_pool_t *pool) 721{ 722 svn_node_kind_t kind; 723 apr_pool_t *iterpool = svn_pool_create(pool); 724 725 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind, 726 iterpool)); 727 728 while (kind == svn_node_none) 729 { 730 svn_pool_clear(iterpool); 731 732 APR_ARRAY_PUSH(new_dirs, const char *) = dir; 733 dir = svn_dirent_dirname(dir, pool); 734 735 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, 736 &kind, iterpool)); 737 } 738 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 " 742 "directory"), dir); 743 744 svn_pool_destroy(iterpool); 745 return SVN_NO_ERROR; 746} 747 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 752 directory. 753 754 Set *TOP_DST_URL and the RA session's root to the existing node's URL. 755 756 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 757 ### implementation susceptible to race conditions. */ 758static svn_error_t * 759find_absent_parents2(svn_ra_session_t *ra_session, 760 const char **top_dst_url, 761 apr_array_header_t *new_dirs, 762 apr_pool_t *pool) 763{ 764 const char *root_url = *top_dst_url; 765 svn_node_kind_t kind; 766 767 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 768 pool)); 769 770 while (kind == svn_node_none) 771 { 772 APR_ARRAY_PUSH(new_dirs, const char *) = root_url; 773 root_url = svn_uri_dirname(root_url, pool); 774 775 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool)); 776 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 777 pool)); 778 } 779 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"), 783 root_url); 784 785 *top_dst_url = root_url; 786 return SVN_NO_ERROR; 787} 788 789static svn_error_t * 790repos_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, 794 void *commit_baton, 795 svn_client_ctx_t *ctx, 796 svn_boolean_t is_move, 797 apr_pool_t *pool) 798{ 799 svn_error_t *err; 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; 808 void *edit_baton; 809 struct path_driver_cb_baton cb_baton; 810 apr_array_header_t *new_dirs = NULL; 811 apr_hash_t *commit_revprops; 812 int i; 813 svn_client__copy_pair_t *first_pair = 814 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 815 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, 822 ctx, pool, pool)); 823 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); 824 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++) 832 { 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; 837 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.")); 845 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, 850 &pair->src_revnum, 851 NULL, NULL, 852 ra_session, 853 pair->src_abspath_or_url, 854 &pair->src_peg_revision, 855 &pair->src_op_revision, NULL, 856 ctx, pool)); 857 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)); 864 if (mergeinfo) 865 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool)); 866 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; 872 } 873 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, 883 pool)); 884 top_url = is_move ? top_url_all : top_url_dst; 885 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++) 890 { 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 *); 895 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; 899 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. 905 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)) 913 { 914 top_url = svn_uri_dirname(top_url, pool); 915 } 916 if (is_move 917 && (strcmp(top_url, pair->src_abspath_or_url) == 0) 918 && (strcmp(top_url, repos_root) != 0)) 919 { 920 top_url = svn_uri_dirname(top_url, pool); 921 } 922 } 923 924 /* Point the RA session to our current TOP_URL. */ 925 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 926 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. */ 930 if (make_parents) 931 { 932 new_dirs = apr_array_make(pool, 0, sizeof(const char *)); 933 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. */ 946 if (is_move) 947 { 948 /* Imagine a situation where the user tries to copy an 949 existing source directory to nonexistent directory with 950 --parents options specified: 951 952 svn copy --parents URL/src URL/dst 953 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 958 function. */ 959 const char *dir = svn_uri_skip_ancestor( 960 top_url, 961 svn_uri_dirname(first_pair->dst_abspath_or_url, 962 pool), 963 pool); 964 if (dir && *dir) 965 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool)); 966 } 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. */ 973 else 974 { 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)); 978 979 /* Convert absolute URLs into relpaths relative to TOP_URL. */ 980 for (i = 0; i < new_urls->nelts; i++) 981 { 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); 984 985 APR_ARRAY_PUSH(new_dirs, const char *) = dir; 986 } 987 } 988 } 989 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 994 the DST_URL. */ 995 for (i = 0; i < copy_pairs->nelts; i++) 996 { 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, 1003 pool); 1004 1005 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0) 1006 && (relpath != NULL && *relpath != '\0')) 1007 { 1008 info->resurrection = TRUE; 1009 top_url = svn_uri_get_longest_ancestor( 1010 top_url, 1011 svn_uri_dirname(pair->dst_abspath_or_url, pool), 1012 pool); 1013 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 1014 } 1015 } 1016 1017 /* Get the portions of the SRC and DST URLs that are relative to 1018 TOP_URL (URI-decoding them while we're at it), verify that the 1019 source exists and the proposed destination does not, and toss 1020 what we've learned into the INFO array. (For copies -- that is, 1021 non-moves -- the relative source URL NULL because it isn't a 1022 child of the TOP_URL at all. That's okay, we'll deal with 1023 it.) */ 1024 for (i = 0; i < copy_pairs->nelts; i++) 1025 { 1026 svn_client__copy_pair_t *pair = 1027 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 1028 path_driver_info_t *info = 1029 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); 1030 svn_node_kind_t dst_kind; 1031 const char *src_rel, *dst_rel; 1032 1033 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool); 1034 if (src_rel) 1035 { 1036 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 1037 &info->src_kind, pool)); 1038 } 1039 else 1040 { 1041 const char *old_url; 1042 1043 src_rel = NULL; 1044 SVN_ERR_ASSERT(! is_move); 1045 1046 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, 1047 pair->src_abspath_or_url, 1048 pool)); 1049 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum, 1050 &info->src_kind, pool)); 1051 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool)); 1052 } 1053 if (info->src_kind == svn_node_none) 1054 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1055 _("Path '%s' does not exist in revision %ld"), 1056 pair->src_abspath_or_url, pair->src_revnum); 1057 1058 /* Figure out the basename that will result from this operation, 1059 and ensure that we aren't trying to overwrite existing paths. */ 1060 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool); 1061 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 1062 &dst_kind, pool)); 1063 if (dst_kind != svn_node_none) 1064 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1065 _("Path '%s' already exists"), dst_rel); 1066 1067 /* More info for our INFO structure. */ 1068 info->src_path = src_rel; 1069 info->dst_path = dst_rel; 1070 1071 svn_hash_sets(action_hash, info->dst_path, info); 1072 if (is_move && (! info->resurrection)) 1073 svn_hash_sets(action_hash, info->src_path, info); 1074 } 1075 1076 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1077 { 1078 /* Produce a list of new paths to add, and provide it to the 1079 mechanism used to acquire a log message. */ 1080 svn_client_commit_item3_t *item; 1081 const char *tmp_file; 1082 apr_array_header_t *commit_items 1083 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item)); 1084 1085 /* Add any intermediate directories to the message */ 1086 if (make_parents) 1087 { 1088 for (i = 0; i < new_dirs->nelts; i++) 1089 { 1090 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1091 1092 item = svn_client_commit_item3_create(pool); 1093 item->url = svn_path_url_add_component2(top_url, relpath, pool); 1094 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1095 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1096 } 1097 } 1098 1099 for (i = 0; i < path_infos->nelts; i++) 1100 { 1101 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1102 path_driver_info_t *); 1103 1104 item = svn_client_commit_item3_create(pool); 1105 item->url = svn_path_url_add_component2(top_url, info->dst_path, 1106 pool); 1107 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1108 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1109 1110 if (is_move && (! info->resurrection)) 1111 { 1112 item = apr_pcalloc(pool, sizeof(*item)); 1113 item->url = svn_path_url_add_component2(top_url, info->src_path, 1114 pool); 1115 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1116 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1117 } 1118 } 1119 1120 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 1121 ctx, pool)); 1122 if (! message) 1123 return SVN_NO_ERROR; 1124 } 1125 else 1126 message = ""; 1127 1128 /* Setup our PATHS for the path-based editor drive. */ 1129 /* First any intermediate directories. */ 1130 if (make_parents) 1131 { 1132 for (i = 0; i < new_dirs->nelts; i++) 1133 { 1134 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1135 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 1136 1137 info->dst_path = relpath; 1138 info->dir_add = TRUE; 1139 1140 APR_ARRAY_PUSH(paths, const char *) = relpath; 1141 svn_hash_sets(action_hash, relpath, info); 1142 } 1143 } 1144 1145 /* Then our copy destinations and move sources (if any). */ 1146 for (i = 0; i < path_infos->nelts; i++) 1147 { 1148 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1149 path_driver_info_t *); 1150 1151 APR_ARRAY_PUSH(paths, const char *) = info->dst_path; 1152 if (is_move && (! info->resurrection)) 1153 APR_ARRAY_PUSH(paths, const char *) = info->src_path; 1154 } 1155 1156 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1157 message, ctx, pool)); 1158 1159 /* Fetch RA commit editor. */ 1160 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1161 svn_client__get_shim_callbacks(ctx->wc_ctx, 1162 NULL, pool))); 1163 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1164 commit_revprops, 1165 commit_callback, 1166 commit_baton, 1167 NULL, TRUE, /* No lock tokens */ 1168 pool)); 1169 1170 /* Setup the callback baton. */ 1171 cb_baton.editor = editor; 1172 cb_baton.edit_baton = edit_baton; 1173 cb_baton.action_hash = action_hash; 1174 cb_baton.is_move = is_move; 1175 1176 /* Call the path-based editor driver. */ 1177 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1178 path_driver_cb_func, &cb_baton, pool); 1179 if (err) 1180 { 1181 /* At least try to abort the edit (and fs txn) before throwing err. */ 1182 return svn_error_compose_create( 1183 err, 1184 editor->abort_edit(edit_baton, pool)); 1185 } 1186 1187 /* Close the edit. */ 1188 return svn_error_trace(editor->close_edit(edit_baton, pool)); 1189} 1190 1191/* Baton for check_url_kind */ 1192struct check_url_kind_baton 1193{ 1194 svn_ra_session_t *session; 1195 const char *repos_root_url; 1196 svn_boolean_t should_reparent; 1197}; 1198 1199/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */ 1200static svn_error_t * 1201check_url_kind(void *baton, 1202 svn_node_kind_t *kind, 1203 const char *url, 1204 svn_revnum_t revision, 1205 apr_pool_t *scratch_pool) 1206{ 1207 struct check_url_kind_baton *cukb = baton; 1208 1209 /* If we don't have a session or can't use the session, get one */ 1210 if (!svn_uri__is_ancestor(cukb->repos_root_url, url)) 1211 *kind = svn_node_none; 1212 else 1213 { 1214 cukb->should_reparent = TRUE; 1215 1216 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool)); 1217 1218 SVN_ERR(svn_ra_check_path(cukb->session, "", revision, 1219 kind, scratch_pool)); 1220 } 1221 1222 return SVN_NO_ERROR; 1223} 1224 1225/* ### Copy ... 1226 * COMMIT_INFO_P is ... 1227 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath 1228 * and each 'dst_abspath_or_url' is a URL. 1229 * MAKE_PARENTS is ... 1230 * REVPROP_TABLE is ... 1231 * CTX is ... */ 1232static svn_error_t * 1233wc_to_repos_copy(const apr_array_header_t *copy_pairs, 1234 svn_boolean_t make_parents, 1235 const apr_hash_t *revprop_table, 1236 svn_commit_callback2_t commit_callback, 1237 void *commit_baton, 1238 svn_client_ctx_t *ctx, 1239 apr_pool_t *scratch_pool) 1240{ 1241 const char *message; 1242 const char *top_src_path, *top_dst_url; 1243 struct check_url_kind_baton cukb; 1244 const char *top_src_abspath; 1245 svn_ra_session_t *ra_session; 1246 const svn_delta_editor_t *editor; 1247 apr_hash_t *relpath_map = NULL; 1248 void *edit_baton; 1249 svn_client__committables_t *committables; 1250 apr_array_header_t *commit_items; 1251 apr_pool_t *iterpool; 1252 apr_array_header_t *new_dirs = NULL; 1253 apr_hash_t *commit_revprops; 1254 svn_client__copy_pair_t *first_pair; 1255 apr_pool_t *session_pool = svn_pool_create(scratch_pool); 1256 int i; 1257 1258 /* Find the common root of all the source paths */ 1259 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL, 1260 scratch_pool)); 1261 1262 /* Do we need to lock the working copy? 1.6 didn't take a write 1263 lock, but what happens if the working copy changes during the copy 1264 operation? */ 1265 1266 iterpool = svn_pool_create(scratch_pool); 1267 1268 /* Determine the longest common ancestor for the destinations, and open an RA 1269 session to that location. */ 1270 /* ### But why start by getting the _parent_ of the first one? */ 1271 /* --- That works because multiple destinations always point to the same 1272 * directory. I'm rather wondering why we need to find a common 1273 * destination parent here at all, instead of simply getting 1274 * top_dst_url from get_copy_pair_ancestors() above? 1275 * It looks like the entire block of code hanging off this comment 1276 * is redundant. */ 1277 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 1278 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool); 1279 for (i = 1; i < copy_pairs->nelts; i++) 1280 { 1281 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1282 svn_client__copy_pair_t *); 1283 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url, 1284 pair->dst_abspath_or_url, 1285 scratch_pool); 1286 } 1287 1288 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool)); 1289 1290 /* Open a session to help while determining the exact targets */ 1291 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url, 1292 top_src_abspath, NULL, 1293 FALSE /* write_dav_props */, 1294 TRUE /* read_dav_props */, 1295 ctx, 1296 session_pool, session_pool)); 1297 1298 /* If requested, determine the nearest existing parent of the destination, 1299 and reparent the ra session there. */ 1300 if (make_parents) 1301 { 1302 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *)); 1303 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs, 1304 scratch_pool)); 1305 } 1306 1307 /* Figure out the basename that will result from each copy and check to make 1308 sure it doesn't exist already. */ 1309 for (i = 0; i < copy_pairs->nelts; i++) 1310 { 1311 svn_node_kind_t dst_kind; 1312 const char *dst_rel; 1313 svn_client__copy_pair_t *pair = 1314 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 1315 1316 svn_pool_clear(iterpool); 1317 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url, 1318 iterpool); 1319 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 1320 &dst_kind, iterpool)); 1321 if (dst_kind != svn_node_none) 1322 { 1323 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1324 _("Path '%s' already exists"), 1325 pair->dst_abspath_or_url); 1326 } 1327 } 1328 1329 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1330 { 1331 /* Produce a list of new paths to add, and provide it to the 1332 mechanism used to acquire a log message. */ 1333 svn_client_commit_item3_t *item; 1334 const char *tmp_file; 1335 commit_items = apr_array_make(scratch_pool, copy_pairs->nelts, 1336 sizeof(item)); 1337 1338 /* Add any intermediate directories to the message */ 1339 if (make_parents) 1340 { 1341 for (i = 0; i < new_dirs->nelts; i++) 1342 { 1343 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *); 1344 1345 item = svn_client_commit_item3_create(scratch_pool); 1346 item->url = url; 1347 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1348 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1349 } 1350 } 1351 1352 for (i = 0; i < copy_pairs->nelts; i++) 1353 { 1354 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1355 svn_client__copy_pair_t *); 1356 1357 item = svn_client_commit_item3_create(scratch_pool); 1358 item->url = pair->dst_abspath_or_url; 1359 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1360 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1361 } 1362 1363 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 1364 ctx, scratch_pool)); 1365 if (! message) 1366 { 1367 svn_pool_destroy(iterpool); 1368 svn_pool_destroy(session_pool); 1369 return SVN_NO_ERROR; 1370 } 1371 } 1372 else 1373 message = ""; 1374 1375 cukb.session = ra_session; 1376 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool)); 1377 cukb.should_reparent = FALSE; 1378 1379 /* Crawl the working copy for commit items. */ 1380 /* ### TODO: Pass check_url_func for issue #3314 handling */ 1381 SVN_ERR(svn_client__get_copy_committables(&committables, 1382 copy_pairs, 1383 check_url_kind, &cukb, 1384 ctx, scratch_pool, iterpool)); 1385 1386 /* The committables are keyed by the repository root */ 1387 commit_items = svn_hash_gets(committables->by_repository, 1388 cukb.repos_root_url); 1389 SVN_ERR_ASSERT(commit_items != NULL); 1390 1391 if (cukb.should_reparent) 1392 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool)); 1393 1394 /* If we are creating intermediate directories, tack them onto the list 1395 of committables. */ 1396 if (make_parents) 1397 { 1398 for (i = 0; i < new_dirs->nelts; i++) 1399 { 1400 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *); 1401 svn_client_commit_item3_t *item; 1402 1403 item = svn_client_commit_item3_create(scratch_pool); 1404 item->url = url; 1405 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1406 item->incoming_prop_changes = apr_array_make(scratch_pool, 1, 1407 sizeof(svn_prop_t *)); 1408 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1409 } 1410 } 1411 1412 /* ### TODO: This extra loop would be unnecessary if this code lived 1413 ### in svn_client__get_copy_committables(), which is incidentally 1414 ### only used above (so should really be in this source file). */ 1415 for (i = 0; i < copy_pairs->nelts; i++) 1416 { 1417 apr_hash_t *mergeinfo, *wc_mergeinfo; 1418 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1419 svn_client__copy_pair_t *); 1420 svn_client_commit_item3_t *item = 1421 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1422 svn_client__pathrev_t *src_origin; 1423 1424 svn_pool_clear(iterpool); 1425 1426 SVN_ERR(svn_client__wc_node_get_origin(&src_origin, 1427 pair->src_abspath_or_url, 1428 ctx, iterpool, iterpool)); 1429 1430 /* Set the mergeinfo for the destination to the combined merge 1431 info known to the WC and the repository. */ 1432 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1, 1433 sizeof(svn_prop_t *)); 1434 /* Repository mergeinfo (or NULL if it's locally added)... */ 1435 if (src_origin) 1436 SVN_ERR(svn_client__get_repos_mergeinfo( 1437 &mergeinfo, ra_session, src_origin->url, src_origin->rev, 1438 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool)); 1439 else 1440 mergeinfo = NULL; 1441 /* ... and WC mergeinfo. */ 1442 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 1443 pair->src_abspath_or_url, 1444 iterpool, iterpool)); 1445 if (wc_mergeinfo && mergeinfo) 1446 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool, 1447 iterpool)); 1448 else if (! mergeinfo) 1449 mergeinfo = wc_mergeinfo; 1450 if (mergeinfo) 1451 { 1452 /* Push a mergeinfo prop representing MERGEINFO onto the 1453 * OUTGOING_PROP_CHANGES array. */ 1454 1455 svn_prop_t *mergeinfo_prop 1456 = apr_palloc(item->outgoing_prop_changes->pool, 1457 sizeof(svn_prop_t)); 1458 svn_string_t *prop_value; 1459 1460 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo, 1461 item->outgoing_prop_changes->pool)); 1462 1463 mergeinfo_prop->name = SVN_PROP_MERGEINFO; 1464 mergeinfo_prop->value = prop_value; 1465 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) 1466 = mergeinfo_prop; 1467 } 1468 } 1469 1470 /* Sort and condense our COMMIT_ITEMS. */ 1471 SVN_ERR(svn_client__condense_commit_items(&top_dst_url, 1472 commit_items, scratch_pool)); 1473 1474#ifdef ENABLE_EV2_SHIMS 1475 if (commit_items) 1476 { 1477 relpath_map = apr_hash_make(pool); 1478 for (i = 0; i < commit_items->nelts; i++) 1479 { 1480 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i, 1481 svn_client_commit_item3_t *); 1482 const char *relpath; 1483 1484 if (!item->path) 1485 continue; 1486 1487 svn_pool_clear(iterpool); 1488 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL, 1489 ctx->wc_ctx, item->path, FALSE, 1490 scratch_pool, iterpool)); 1491 if (relpath) 1492 svn_hash_sets(relpath_map, relpath, item->path); 1493 } 1494 } 1495#endif 1496 1497 /* Close the initial session, to reopen a new session with commit handling */ 1498 svn_pool_clear(session_pool); 1499 1500 /* Open a new RA session to DST_URL. */ 1501 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url, 1502 NULL, commit_items, 1503 FALSE, FALSE, ctx, 1504 session_pool, session_pool)); 1505 1506 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1507 message, ctx, session_pool)); 1508 1509 /* Fetch RA commit editor. */ 1510 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1511 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map, 1512 session_pool))); 1513 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1514 commit_revprops, 1515 commit_callback, 1516 commit_baton, NULL, 1517 TRUE, /* No lock tokens */ 1518 session_pool)); 1519 1520 /* Perform the commit. */ 1521 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items, 1522 editor, edit_baton, 1523 0, /* ### any notify_path_offset needed? */ 1524 NULL, ctx, session_pool, session_pool), 1525 _("Commit failed (details follow):")); 1526 1527 svn_pool_destroy(iterpool); 1528 svn_pool_destroy(session_pool); 1529 1530 return SVN_NO_ERROR; 1531} 1532 1533/* A baton for notification_adjust_func(). */ 1534struct notification_adjust_baton 1535{ 1536 svn_wc_notify_func2_t inner_func; 1537 void *inner_baton; 1538 const char *checkout_abspath; 1539 const char *final_abspath; 1540}; 1541 1542/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose 1543 * baton is BATON->inner_baton) and adjusts the notification paths that 1544 * start with BATON->checkout_abspath to start instead with 1545 * BATON->final_abspath. */ 1546static void 1547notification_adjust_func(void *baton, 1548 const svn_wc_notify_t *notify, 1549 apr_pool_t *pool) 1550{ 1551 struct notification_adjust_baton *nb = baton; 1552 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool); 1553 const char *relpath; 1554 1555 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); 1556 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); 1557 1558 if (nb->inner_func) 1559 nb->inner_func(nb->inner_baton, inner_notify, pool); 1560} 1561 1562/* Peform each individual copy operation for a repos -> wc copy. A 1563 helper for repos_to_wc_copy(). 1564 1565 Resolve PAIR->src_revnum to a real revision number if it isn't already. */ 1566static svn_error_t * 1567repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, 1568 svn_client__copy_pair_t *pair, 1569 svn_boolean_t same_repositories, 1570 svn_boolean_t ignore_externals, 1571 svn_ra_session_t *ra_session, 1572 svn_client_ctx_t *ctx, 1573 apr_pool_t *pool) 1574{ 1575 apr_hash_t *src_mergeinfo; 1576 const char *dst_abspath = pair->dst_abspath_or_url; 1577 1578 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 1579 1580 if (!same_repositories && ctx->notify_func2) 1581 { 1582 svn_wc_notify_t *notify; 1583 notify = svn_wc_create_notify_url( 1584 pair->src_abspath_or_url, 1585 svn_wc_notify_foreign_copy_begin, 1586 pool); 1587 notify->kind = pair->src_kind; 1588 ctx->notify_func2(ctx->notify_baton2, notify, pool); 1589 1590 /* Allow a theoretical cancel to get through. */ 1591 if (ctx->cancel_func) 1592 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1593 } 1594 1595 if (pair->src_kind == svn_node_dir) 1596 { 1597 if (same_repositories) 1598 { 1599 svn_boolean_t sleep_needed = FALSE; 1600 const char *tmpdir_abspath, *tmp_abspath; 1601 1602 /* Find a temporary location in which to check out the copy source. */ 1603 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath, 1604 pool, pool)); 1605 1606 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, 1607 svn_io_file_del_on_close, pool, pool)); 1608 1609 /* Make a new checkout of the requested source. While doing so, 1610 * resolve pair->src_revnum to an actual revision number in case it 1611 * was until now 'invalid' meaning 'head'. Ask this function not to 1612 * sleep for timestamps, by passing a sleep_needed output param. 1613 * Send notifications for all nodes except the root node, and adjust 1614 * them to refer to the destination rather than this temporary path. */ 1615 { 1616 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; 1617 void *old_notify_baton2 = ctx->notify_baton2; 1618 struct notification_adjust_baton nb; 1619 svn_error_t *err; 1620 1621 nb.inner_func = ctx->notify_func2; 1622 nb.inner_baton = ctx->notify_baton2; 1623 nb.checkout_abspath = tmp_abspath; 1624 nb.final_abspath = dst_abspath; 1625 ctx->notify_func2 = notification_adjust_func; 1626 ctx->notify_baton2 = &nb; 1627 1628 err = svn_client__checkout_internal(&pair->src_revnum, 1629 pair->src_original, 1630 tmp_abspath, 1631 &pair->src_peg_revision, 1632 &pair->src_op_revision, 1633 svn_depth_infinity, 1634 ignore_externals, FALSE, 1635 &sleep_needed, ctx, pool); 1636 1637 ctx->notify_func2 = old_notify_func2; 1638 ctx->notify_baton2 = old_notify_baton2; 1639 1640 SVN_ERR(err); 1641 } 1642 1643 *timestamp_sleep = TRUE; 1644 1645 /* Schedule dst_path for addition in parent, with copy history. 1646 Don't send any notification here. 1647 Then remove the temporary checkout's .svn dir in preparation for 1648 moving the rest of it into the final destination. */ 1649 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath, 1650 TRUE /* metadata_only */, 1651 ctx->cancel_func, ctx->cancel_baton, 1652 NULL, NULL, pool)); 1653 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, 1654 FALSE, pool, pool)); 1655 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx, 1656 tmp_abspath, 1657 FALSE, FALSE, 1658 ctx->cancel_func, 1659 ctx->cancel_baton, 1660 pool)); 1661 1662 /* Move the temporary disk tree into place. */ 1663 SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool)); 1664 } 1665 else 1666 { 1667 *timestamp_sleep = TRUE; 1668 1669 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url, 1670 dst_abspath, 1671 &pair->src_peg_revision, 1672 &pair->src_op_revision, 1673 svn_depth_infinity, 1674 FALSE /* make_parents */, 1675 TRUE /* already_locked */, 1676 ctx, pool)); 1677 1678 return SVN_NO_ERROR; 1679 } 1680 } /* end directory case */ 1681 1682 else if (pair->src_kind == svn_node_file) 1683 { 1684 apr_hash_t *new_props; 1685 const char *src_rel; 1686 svn_stream_t *new_base_contents = svn_stream_buffered(pool); 1687 1688 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 1689 pair->src_abspath_or_url, 1690 pool)); 1691 /* Fetch the file content. While doing so, resolve pair->src_revnum 1692 * to an actual revision number if it's 'invalid' meaning 'head'. */ 1693 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum, 1694 new_base_contents, 1695 &pair->src_revnum, &new_props, pool)); 1696 1697 if (new_props && ! same_repositories) 1698 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); 1699 1700 *timestamp_sleep = TRUE; 1701 1702 SVN_ERR(svn_wc_add_repos_file4( 1703 ctx->wc_ctx, dst_abspath, 1704 new_base_contents, NULL, new_props, NULL, 1705 same_repositories ? pair->src_abspath_or_url : NULL, 1706 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM, 1707 ctx->cancel_func, ctx->cancel_baton, 1708 pool)); 1709 } 1710 1711 /* Record the implied mergeinfo (before the notification callback 1712 is invoked for the root node). */ 1713 SVN_ERR(svn_client__get_repos_mergeinfo( 1714 &src_mergeinfo, ra_session, 1715 pair->src_abspath_or_url, pair->src_revnum, 1716 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 1717 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); 1718 1719 /* Do our own notification for the root node, even if we could possibly 1720 have delegated it. See also issue #1552. 1721 1722 ### Maybe this notification should mention the mergeinfo change. */ 1723 if (ctx->notify_func2) 1724 { 1725 svn_wc_notify_t *notify = svn_wc_create_notify( 1726 dst_abspath, svn_wc_notify_add, pool); 1727 notify->kind = pair->src_kind; 1728 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1729 } 1730 1731 return SVN_NO_ERROR; 1732} 1733 1734static svn_error_t * 1735repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, 1736 const apr_array_header_t *copy_pairs, 1737 const char *top_dst_path, 1738 svn_boolean_t ignore_externals, 1739 svn_ra_session_t *ra_session, 1740 svn_client_ctx_t *ctx, 1741 apr_pool_t *scratch_pool) 1742{ 1743 int i; 1744 svn_boolean_t same_repositories; 1745 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1746 1747 /* We've already checked for physical obstruction by a working file. 1748 But there could also be logical obstruction by an entry whose 1749 working file happens to be missing.*/ 1750 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */, 1751 ctx, scratch_pool, iterpool)); 1752 1753 /* Decide whether the two repositories are the same or not. */ 1754 { 1755 svn_error_t *src_err, *dst_err; 1756 const char *parent; 1757 const char *parent_abspath; 1758 const char *src_uuid, *dst_uuid; 1759 1760 /* Get the repository uuid of SRC_URL */ 1761 src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool); 1762 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 1763 return svn_error_trace(src_err); 1764 1765 /* Get repository uuid of dst's parent directory, since dst may 1766 not exist. ### TODO: we should probably walk up the wc here, 1767 in case the parent dir has an imaginary URL. */ 1768 if (copy_pairs->nelts == 1) 1769 parent = svn_dirent_dirname(top_dst_path, scratch_pool); 1770 else 1771 parent = top_dst_path; 1772 1773 SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool)); 1774 dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid, 1775 parent_abspath, ctx, 1776 iterpool, iterpool); 1777 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 1778 return dst_err; 1779 1780 /* If either of the UUIDs are nonexistent, then at least one of 1781 the repositories must be very old. Rather than punish the 1782 user, just assume the repositories are different, so no 1783 copy-history is attempted. */ 1784 if (src_err || dst_err || (! src_uuid) || (! dst_uuid)) 1785 same_repositories = FALSE; 1786 else 1787 same_repositories = (strcmp(src_uuid, dst_uuid) == 0); 1788 } 1789 1790 /* Perform the move for each of the copy_pairs. */ 1791 for (i = 0; i < copy_pairs->nelts; i++) 1792 { 1793 /* Check for cancellation */ 1794 if (ctx->cancel_func) 1795 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1796 1797 svn_pool_clear(iterpool); 1798 1799 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep, 1800 APR_ARRAY_IDX(copy_pairs, i, 1801 svn_client__copy_pair_t *), 1802 same_repositories, 1803 ignore_externals, 1804 ra_session, ctx, iterpool)); 1805 } 1806 svn_pool_destroy(iterpool); 1807 1808 return SVN_NO_ERROR; 1809} 1810 1811static svn_error_t * 1812repos_to_wc_copy(svn_boolean_t *timestamp_sleep, 1813 const apr_array_header_t *copy_pairs, 1814 svn_boolean_t make_parents, 1815 svn_boolean_t ignore_externals, 1816 svn_client_ctx_t *ctx, 1817 apr_pool_t *pool) 1818{ 1819 svn_ra_session_t *ra_session; 1820 const char *top_src_url, *top_dst_path; 1821 apr_pool_t *iterpool = svn_pool_create(pool); 1822 const char *lock_abspath; 1823 int i; 1824 1825 /* Get the real path for the source, based upon its peg revision. */ 1826 for (i = 0; i < copy_pairs->nelts; i++) 1827 { 1828 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1829 svn_client__copy_pair_t *); 1830 const char *src; 1831 1832 svn_pool_clear(iterpool); 1833 1834 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL, 1835 NULL, 1836 pair->src_abspath_or_url, 1837 &pair->src_peg_revision, 1838 &pair->src_op_revision, NULL, 1839 ctx, iterpool)); 1840 1841 pair->src_original = pair->src_abspath_or_url; 1842 pair->src_abspath_or_url = apr_pstrdup(pool, src); 1843 } 1844 1845 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path, 1846 NULL, pool)); 1847 lock_abspath = top_dst_path; 1848 if (copy_pairs->nelts == 1) 1849 { 1850 top_src_url = svn_uri_dirname(top_src_url, pool); 1851 lock_abspath = svn_dirent_dirname(top_dst_path, pool); 1852 } 1853 1854 /* Open a repository session to the longest common src ancestor. We do not 1855 (yet) have a working copy, so we don't have a corresponding path and 1856 tempfiles cannot go into the admin area. */ 1857 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath, 1858 ctx, pool, pool)); 1859 1860 /* Get the correct src path for the peg revision used, and verify that we 1861 aren't overwriting an existing path. */ 1862 for (i = 0; i < copy_pairs->nelts; i++) 1863 { 1864 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1865 svn_client__copy_pair_t *); 1866 svn_node_kind_t dst_parent_kind, dst_kind; 1867 const char *dst_parent; 1868 const char *src_rel; 1869 1870 svn_pool_clear(iterpool); 1871 1872 /* Next, make sure that the path exists in the repository. */ 1873 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 1874 pair->src_abspath_or_url, 1875 iterpool)); 1876 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 1877 &pair->src_kind, pool)); 1878 if (pair->src_kind == svn_node_none) 1879 { 1880 if (SVN_IS_VALID_REVNUM(pair->src_revnum)) 1881 return svn_error_createf 1882 (SVN_ERR_FS_NOT_FOUND, NULL, 1883 _("Path '%s' not found in revision %ld"), 1884 pair->src_abspath_or_url, pair->src_revnum); 1885 else 1886 return svn_error_createf 1887 (SVN_ERR_FS_NOT_FOUND, NULL, 1888 _("Path '%s' not found in head revision"), 1889 pair->src_abspath_or_url); 1890 } 1891 1892 /* Figure out about dst. */ 1893 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 1894 iterpool)); 1895 if (dst_kind != svn_node_none) 1896 { 1897 return svn_error_createf( 1898 SVN_ERR_ENTRY_EXISTS, NULL, 1899 _("Path '%s' already exists"), 1900 svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 1901 } 1902 1903 /* Make sure the destination parent is a directory and produce a clear 1904 error message if it is not. */ 1905 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool); 1906 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool)); 1907 if (make_parents && dst_parent_kind == svn_node_none) 1908 { 1909 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, 1910 iterpool)); 1911 } 1912 else if (dst_parent_kind != svn_node_dir) 1913 { 1914 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 1915 _("Path '%s' is not a directory"), 1916 svn_dirent_local_style(dst_parent, pool)); 1917 } 1918 } 1919 svn_pool_destroy(iterpool); 1920 1921 SVN_WC__CALL_WITH_WRITE_LOCK( 1922 repos_to_wc_copy_locked(timestamp_sleep, 1923 copy_pairs, top_dst_path, ignore_externals, 1924 ra_session, ctx, pool), 1925 ctx->wc_ctx, lock_abspath, FALSE, pool); 1926 return SVN_NO_ERROR; 1927} 1928 1929#define NEED_REPOS_REVNUM(revision) \ 1930 ((revision.kind != svn_opt_revision_unspecified) \ 1931 && (revision.kind != svn_opt_revision_working)) 1932 1933/* ... 1934 * 1935 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not 1936 * change *TIMESTAMP_SLEEP. This output will be valid even if the 1937 * function returns an error. 1938 * 1939 * Perform all allocations in POOL. 1940 */ 1941static svn_error_t * 1942try_copy(svn_boolean_t *timestamp_sleep, 1943 const apr_array_header_t *sources, 1944 const char *dst_path_in, 1945 svn_boolean_t is_move, 1946 svn_boolean_t allow_mixed_revisions, 1947 svn_boolean_t metadata_only, 1948 svn_boolean_t make_parents, 1949 svn_boolean_t ignore_externals, 1950 const apr_hash_t *revprop_table, 1951 svn_commit_callback2_t commit_callback, 1952 void *commit_baton, 1953 svn_client_ctx_t *ctx, 1954 apr_pool_t *pool) 1955{ 1956 apr_array_header_t *copy_pairs = 1957 apr_array_make(pool, sources->nelts, 1958 sizeof(svn_client__copy_pair_t *)); 1959 svn_boolean_t srcs_are_urls, dst_is_url; 1960 int i; 1961 1962 /* Are either of our paths URLs? Just check the first src_path. If 1963 there are more than one, we'll check for homogeneity among them 1964 down below. */ 1965 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0, 1966 svn_client_copy_source_t *)->path); 1967 dst_is_url = svn_path_is_url(dst_path_in); 1968 if (!dst_is_url) 1969 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool)); 1970 1971 /* If we have multiple source paths, it implies the dst_path is a 1972 directory we are moving or copying into. Populate the COPY_PAIRS 1973 array to contain a destination path for each of the source paths. */ 1974 if (sources->nelts > 1) 1975 { 1976 apr_pool_t *iterpool = svn_pool_create(pool); 1977 1978 for (i = 0; i < sources->nelts; i++) 1979 { 1980 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i, 1981 svn_client_copy_source_t *); 1982 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair)); 1983 const char *src_basename; 1984 svn_boolean_t src_is_url = svn_path_is_url(source->path); 1985 1986 svn_pool_clear(iterpool); 1987 1988 if (src_is_url) 1989 { 1990 pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 1991 src_basename = svn_uri_basename(pair->src_abspath_or_url, 1992 iterpool); 1993 } 1994 else 1995 { 1996 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 1997 source->path, pool)); 1998 src_basename = svn_dirent_basename(pair->src_abspath_or_url, 1999 iterpool); 2000 } 2001 2002 pair->src_op_revision = *source->revision; 2003 pair->src_peg_revision = *source->peg_revision; 2004 2005 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2006 &pair->src_op_revision, 2007 src_is_url, 2008 TRUE, 2009 iterpool)); 2010 2011 /* Check to see if all the sources are urls or all working copy 2012 * paths. */ 2013 if (src_is_url != srcs_are_urls) 2014 return svn_error_create 2015 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2016 _("Cannot mix repository and working copy sources")); 2017 2018 if (dst_is_url) 2019 pair->dst_abspath_or_url = 2020 svn_path_url_add_component2(dst_path_in, src_basename, pool); 2021 else 2022 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in, 2023 src_basename, pool); 2024 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2025 } 2026 2027 svn_pool_destroy(iterpool); 2028 } 2029 else 2030 { 2031 /* Only one source path. */ 2032 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair)); 2033 svn_client_copy_source_t *source = 2034 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *); 2035 svn_boolean_t src_is_url = svn_path_is_url(source->path); 2036 2037 if (src_is_url) 2038 pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 2039 else 2040 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 2041 source->path, pool)); 2042 pair->src_op_revision = *source->revision; 2043 pair->src_peg_revision = *source->peg_revision; 2044 2045 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2046 &pair->src_op_revision, 2047 src_is_url, TRUE, pool)); 2048 2049 pair->dst_abspath_or_url = dst_path_in; 2050 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2051 } 2052 2053 if (!srcs_are_urls && !dst_is_url) 2054 { 2055 apr_pool_t *iterpool = svn_pool_create(pool); 2056 2057 for (i = 0; i < copy_pairs->nelts; i++) 2058 { 2059 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2060 svn_client__copy_pair_t *); 2061 2062 svn_pool_clear(iterpool); 2063 2064 if (svn_dirent_is_child(pair->src_abspath_or_url, 2065 pair->dst_abspath_or_url, iterpool)) 2066 return svn_error_createf 2067 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2068 _("Cannot copy path '%s' into its own child '%s'"), 2069 svn_dirent_local_style(pair->src_abspath_or_url, pool), 2070 svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 2071 } 2072 2073 svn_pool_destroy(iterpool); 2074 } 2075 2076 /* A file external should not be moved since the file external is 2077 implemented as a switched file and it would delete the file the 2078 file external is switched to, which is not the behavior the user 2079 would probably want. */ 2080 if (is_move && !srcs_are_urls) 2081 { 2082 apr_pool_t *iterpool = svn_pool_create(pool); 2083 2084 for (i = 0; i < copy_pairs->nelts; i++) 2085 { 2086 svn_client__copy_pair_t *pair = 2087 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 2088 svn_node_kind_t external_kind; 2089 const char *defining_abspath; 2090 2091 svn_pool_clear(iterpool); 2092 2093 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 2094 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, 2095 NULL, NULL, NULL, ctx->wc_ctx, 2096 pair->src_abspath_or_url, 2097 pair->src_abspath_or_url, TRUE, 2098 iterpool, iterpool)); 2099 2100 if (external_kind != svn_node_none) 2101 return svn_error_createf( 2102 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL, 2103 NULL, 2104 _("Cannot move the external at '%s'; please " 2105 "edit the svn:externals property on '%s'."), 2106 svn_dirent_local_style(pair->src_abspath_or_url, pool), 2107 svn_dirent_local_style(defining_abspath, pool)); 2108 } 2109 svn_pool_destroy(iterpool); 2110 } 2111 2112 if (is_move) 2113 { 2114 /* Disallow moves between the working copy and the repository. */ 2115 if (srcs_are_urls != dst_is_url) 2116 { 2117 return svn_error_create 2118 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2119 _("Moves between the working copy and the repository are not " 2120 "supported")); 2121 } 2122 2123 /* Disallow moving any path/URL onto or into itself. */ 2124 for (i = 0; i < copy_pairs->nelts; i++) 2125 { 2126 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2127 svn_client__copy_pair_t *); 2128 2129 if (strcmp(pair->src_abspath_or_url, 2130 pair->dst_abspath_or_url) == 0) 2131 return svn_error_createf( 2132 SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2133 srcs_are_urls ? 2134 _("Cannot move URL '%s' into itself") : 2135 _("Cannot move path '%s' into itself"), 2136 srcs_are_urls ? 2137 pair->src_abspath_or_url : 2138 svn_dirent_local_style(pair->src_abspath_or_url, pool)); 2139 } 2140 } 2141 else 2142 { 2143 if (!srcs_are_urls) 2144 { 2145 /* If we are doing a wc->* copy, but with an operational revision 2146 other than the working copy revision, we are really doing a 2147 repo->* copy, because we're going to need to get the rev from the 2148 repo. */ 2149 2150 svn_boolean_t need_repos_op_rev = FALSE; 2151 svn_boolean_t need_repos_peg_rev = FALSE; 2152 2153 /* Check to see if any revision is something other than 2154 svn_opt_revision_unspecified or svn_opt_revision_working. */ 2155 for (i = 0; i < copy_pairs->nelts; i++) 2156 { 2157 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2158 svn_client__copy_pair_t *); 2159 2160 if (NEED_REPOS_REVNUM(pair->src_op_revision)) 2161 need_repos_op_rev = TRUE; 2162 2163 if (NEED_REPOS_REVNUM(pair->src_peg_revision)) 2164 need_repos_peg_rev = TRUE; 2165 2166 if (need_repos_op_rev || need_repos_peg_rev) 2167 break; 2168 } 2169 2170 if (need_repos_op_rev || need_repos_peg_rev) 2171 { 2172 apr_pool_t *iterpool = svn_pool_create(pool); 2173 2174 for (i = 0; i < copy_pairs->nelts; i++) 2175 { 2176 const char *copyfrom_repos_root_url; 2177 const char *copyfrom_repos_relpath; 2178 const char *url; 2179 svn_revnum_t copyfrom_rev; 2180 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2181 svn_client__copy_pair_t *); 2182 2183 svn_pool_clear(iterpool); 2184 2185 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 2186 2187 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev, 2188 ©from_repos_relpath, 2189 ©from_repos_root_url, 2190 NULL, NULL, 2191 ctx->wc_ctx, 2192 pair->src_abspath_or_url, 2193 TRUE, iterpool, iterpool)); 2194 2195 if (copyfrom_repos_relpath) 2196 url = svn_path_url_add_component2(copyfrom_repos_root_url, 2197 copyfrom_repos_relpath, 2198 pool); 2199 else 2200 return svn_error_createf 2201 (SVN_ERR_ENTRY_MISSING_URL, NULL, 2202 _("'%s' does not have a URL associated with it"), 2203 svn_dirent_local_style(pair->src_abspath_or_url, pool)); 2204 2205 pair->src_abspath_or_url = url; 2206 2207 if (!need_repos_peg_rev 2208 || pair->src_peg_revision.kind == svn_opt_revision_base) 2209 { 2210 /* Default the peg revision to that of the WC entry. */ 2211 pair->src_peg_revision.kind = svn_opt_revision_number; 2212 pair->src_peg_revision.value.number = copyfrom_rev; 2213 } 2214 2215 if (pair->src_op_revision.kind == svn_opt_revision_base) 2216 { 2217 /* Use the entry's revision as the operational rev. */ 2218 pair->src_op_revision.kind = svn_opt_revision_number; 2219 pair->src_op_revision.value.number = copyfrom_rev; 2220 } 2221 } 2222 2223 svn_pool_destroy(iterpool); 2224 srcs_are_urls = TRUE; 2225 } 2226 } 2227 } 2228 2229 /* Now, call the right handler for the operation. */ 2230 if ((! srcs_are_urls) && (! dst_is_url)) 2231 { 2232 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move, 2233 metadata_only, ctx, pool, pool)); 2234 2235 /* Copy or move all targets. */ 2236 if (is_move) 2237 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep, 2238 copy_pairs, dst_path_in, 2239 allow_mixed_revisions, 2240 metadata_only, 2241 ctx, pool)); 2242 else 2243 { 2244 /* We ignore these values, so assert the default value */ 2245 SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only); 2246 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep, 2247 copy_pairs, ctx, pool)); 2248 } 2249 } 2250 else if ((! srcs_are_urls) && (dst_is_url)) 2251 { 2252 return svn_error_trace( 2253 wc_to_repos_copy(copy_pairs, make_parents, revprop_table, 2254 commit_callback, commit_baton, ctx, pool)); 2255 } 2256 else if ((srcs_are_urls) && (! dst_is_url)) 2257 { 2258 return svn_error_trace( 2259 repos_to_wc_copy(timestamp_sleep, 2260 copy_pairs, make_parents, ignore_externals, 2261 ctx, pool)); 2262 } 2263 else 2264 { 2265 return svn_error_trace( 2266 repos_to_repos_copy(copy_pairs, make_parents, revprop_table, 2267 commit_callback, commit_baton, ctx, is_move, 2268 pool)); 2269 } 2270} 2271 2272 2273 2274/* Public Interfaces */ 2275svn_error_t * 2276svn_client_copy6(const apr_array_header_t *sources, 2277 const char *dst_path, 2278 svn_boolean_t copy_as_child, 2279 svn_boolean_t make_parents, 2280 svn_boolean_t ignore_externals, 2281 const apr_hash_t *revprop_table, 2282 svn_commit_callback2_t commit_callback, 2283 void *commit_baton, 2284 svn_client_ctx_t *ctx, 2285 apr_pool_t *pool) 2286{ 2287 svn_error_t *err; 2288 svn_boolean_t timestamp_sleep = FALSE; 2289 apr_pool_t *subpool = svn_pool_create(pool); 2290 2291 if (sources->nelts > 1 && !copy_as_child) 2292 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 2293 NULL, NULL); 2294 2295 err = try_copy(×tamp_sleep, 2296 sources, dst_path, 2297 FALSE /* is_move */, 2298 TRUE /* allow_mixed_revisions */, 2299 FALSE /* metadata_only */, 2300 make_parents, 2301 ignore_externals, 2302 revprop_table, 2303 commit_callback, commit_baton, 2304 ctx, 2305 subpool); 2306 2307 /* If the destination exists, try to copy the sources as children of the 2308 destination. */ 2309 if (copy_as_child && err && (sources->nelts == 1) 2310 && (err->apr_err == SVN_ERR_ENTRY_EXISTS 2311 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 2312 { 2313 const char *src_path = APR_ARRAY_IDX(sources, 0, 2314 svn_client_copy_source_t *)->path; 2315 const char *src_basename; 2316 svn_boolean_t src_is_url = svn_path_is_url(src_path); 2317 svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 2318 2319 svn_error_clear(err); 2320 svn_pool_clear(subpool); 2321 2322 src_basename = src_is_url ? svn_uri_basename(src_path, subpool) 2323 : svn_dirent_basename(src_path, subpool); 2324 dst_path 2325 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 2326 subpool) 2327 : svn_dirent_join(dst_path, src_basename, subpool); 2328 2329 err = try_copy(×tamp_sleep, 2330 sources, dst_path, 2331 FALSE /* is_move */, 2332 TRUE /* allow_mixed_revisions */, 2333 FALSE /* metadata_only */, 2334 make_parents, 2335 ignore_externals, 2336 revprop_table, 2337 commit_callback, commit_baton, 2338 ctx, 2339 subpool); 2340 } 2341 2342 /* Sleep if required. DST_PATH is not a URL in these cases. */ 2343 if (timestamp_sleep) 2344 svn_io_sleep_for_timestamps(dst_path, subpool); 2345 2346 svn_pool_destroy(subpool); 2347 return svn_error_trace(err); 2348} 2349 2350 2351svn_error_t * 2352svn_client_move7(const apr_array_header_t *src_paths, 2353 const char *dst_path, 2354 svn_boolean_t move_as_child, 2355 svn_boolean_t make_parents, 2356 svn_boolean_t allow_mixed_revisions, 2357 svn_boolean_t metadata_only, 2358 const apr_hash_t *revprop_table, 2359 svn_commit_callback2_t commit_callback, 2360 void *commit_baton, 2361 svn_client_ctx_t *ctx, 2362 apr_pool_t *pool) 2363{ 2364 const svn_opt_revision_t head_revision 2365 = { svn_opt_revision_head, { 0 } }; 2366 svn_error_t *err; 2367 svn_boolean_t timestamp_sleep = FALSE; 2368 int i; 2369 apr_pool_t *subpool = svn_pool_create(pool); 2370 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts, 2371 sizeof(const svn_client_copy_source_t *)); 2372 2373 if (src_paths->nelts > 1 && !move_as_child) 2374 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 2375 NULL, NULL); 2376 2377 for (i = 0; i < src_paths->nelts; i++) 2378 { 2379 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *); 2380 svn_client_copy_source_t *copy_source = apr_palloc(pool, 2381 sizeof(*copy_source)); 2382 2383 copy_source->path = src_path; 2384 copy_source->revision = &head_revision; 2385 copy_source->peg_revision = &head_revision; 2386 2387 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source; 2388 } 2389 2390 err = try_copy(×tamp_sleep, 2391 sources, dst_path, 2392 TRUE /* is_move */, 2393 allow_mixed_revisions, 2394 metadata_only, 2395 make_parents, 2396 FALSE /* ignore_externals */, 2397 revprop_table, 2398 commit_callback, commit_baton, 2399 ctx, 2400 subpool); 2401 2402 /* If the destination exists, try to move the sources as children of the 2403 destination. */ 2404 if (move_as_child && err && (src_paths->nelts == 1) 2405 && (err->apr_err == SVN_ERR_ENTRY_EXISTS 2406 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 2407 { 2408 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *); 2409 const char *src_basename; 2410 svn_boolean_t src_is_url = svn_path_is_url(src_path); 2411 svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 2412 2413 svn_error_clear(err); 2414 svn_pool_clear(subpool); 2415 2416 src_basename = src_is_url ? svn_uri_basename(src_path, pool) 2417 : svn_dirent_basename(src_path, pool); 2418 dst_path 2419 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 2420 subpool) 2421 : svn_dirent_join(dst_path, src_basename, subpool); 2422 2423 err = try_copy(×tamp_sleep, 2424 sources, dst_path, 2425 TRUE /* is_move */, 2426 allow_mixed_revisions, 2427 metadata_only, 2428 make_parents, 2429 FALSE /* ignore_externals */, 2430 revprop_table, 2431 commit_callback, commit_baton, 2432 ctx, 2433 subpool); 2434 } 2435 2436 /* Sleep if required. DST_PATH is not a URL in these cases. */ 2437 if (timestamp_sleep) 2438 svn_io_sleep_for_timestamps(dst_path, subpool); 2439 2440 svn_pool_destroy(subpool); 2441 return svn_error_trace(err); 2442} 2443