1/* 2 * copy.c: 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_pools.h" 32#include "svn_error.h" 33#include "svn_dirent_uri.h" 34#include "svn_path.h" 35#include "svn_hash.h" 36 37#include "wc.h" 38#include "workqueue.h" 39#include "props.h" 40#include "conflicts.h" 41 42#include "svn_private_config.h" 43#include "private/svn_wc_private.h" 44 45 46/*** Code. ***/ 47 48/* Make a copy of the filesystem node (or tree if RECURSIVE) at 49 SRC_ABSPATH under a temporary name in the directory 50 TMPDIR_ABSPATH and return the absolute path of the copy in 51 *DST_ABSPATH. Return the node kind of SRC_ABSPATH in *KIND. If 52 SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate 53 that no copy was made. */ 54static svn_error_t * 55copy_to_tmpdir(svn_skel_t **work_item, 56 svn_node_kind_t *kind, 57 svn_wc__db_t *db, 58 const char *src_abspath, 59 const char *dst_abspath, 60 const char *tmpdir_abspath, 61 svn_boolean_t file_copy, 62 svn_boolean_t unversioned, 63 svn_cancel_func_t cancel_func, 64 void *cancel_baton, 65 apr_pool_t *result_pool, 66 apr_pool_t *scratch_pool) 67{ 68 svn_boolean_t is_special; 69 svn_io_file_del_t delete_when; 70 const char *dst_tmp_abspath; 71 svn_node_kind_t dsk_kind; 72 if (!kind) 73 kind = &dsk_kind; 74 75 *work_item = NULL; 76 77 SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special, 78 scratch_pool)); 79 if (*kind == svn_node_none) 80 { 81 return SVN_NO_ERROR; 82 } 83 else if (*kind == svn_node_unknown) 84 { 85 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 86 _("Source '%s' is unexpected kind"), 87 svn_dirent_local_style(src_abspath, 88 scratch_pool)); 89 } 90 else if (*kind == svn_node_dir || is_special) 91 delete_when = svn_io_file_del_on_close; 92 else /* the default case: (*kind == svn_node_file) */ 93 delete_when = svn_io_file_del_none; 94 95 /* ### Do we need a pool cleanup to remove the copy? We can't use 96 ### svn_io_file_del_on_pool_cleanup above because a) it won't 97 ### handle the directory case and b) we need to be able to remove 98 ### the cleanup before queueing the move work item. */ 99 100 if (file_copy && !unversioned) 101 { 102 svn_boolean_t modified; 103 /* It's faster to look for mods on the source now, as 104 the timestamp might match, than to examine the 105 destination later as the destination timestamp will 106 never match. */ 107 SVN_ERR(svn_wc__internal_file_modified_p(&modified, 108 db, src_abspath, 109 FALSE, scratch_pool)); 110 if (!modified) 111 { 112 /* Why create a temp copy if we can just reinstall from pristine? */ 113 SVN_ERR(svn_wc__wq_build_file_install(work_item, 114 db, dst_abspath, NULL, FALSE, 115 TRUE, 116 result_pool, scratch_pool)); 117 return SVN_NO_ERROR; 118 } 119 } 120 121 /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave 122 a file there and then overwrite it; otherwise leave no node on disk at 123 that path. In the latter case, something else might use that path 124 before we get around to using it a moment later, but never mind. */ 125 SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath, 126 delete_when, scratch_pool, scratch_pool)); 127 128 if (*kind == svn_node_dir) 129 { 130 if (file_copy) 131 SVN_ERR(svn_io_copy_dir_recursively( 132 src_abspath, 133 tmpdir_abspath, 134 svn_dirent_basename(dst_tmp_abspath, scratch_pool), 135 TRUE, /* copy_perms */ 136 cancel_func, cancel_baton, 137 scratch_pool)); 138 else 139 SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool)); 140 } 141 else if (!is_special) 142 SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath, 143 TRUE /* copy_perms */, 144 scratch_pool)); 145 else 146 SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool)); 147 148 if (file_copy) 149 { 150 /* Remove 'read-only' from the destination file; it's a local add now. */ 151 SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath, 152 FALSE, scratch_pool)); 153 } 154 155 SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath, 156 dst_tmp_abspath, dst_abspath, 157 result_pool, scratch_pool)); 158 159 return SVN_NO_ERROR; 160} 161 162/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB. 163 If METADATA_ONLY is true, copy only the versioned metadata, 164 otherwise copy both the versioned metadata and the filesystem node (even 165 if it is the wrong kind, and recursively if it is a dir). 166 167 If IS_MOVE is true, record move information in working copy meta 168 data in addition to copying the file. 169 170 If the versioned file has a text conflict, and the .mine file exists in 171 the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the 172 versioned file itself. 173 174 This also works for versioned symlinks that are stored in the db as 175 svn_node_file with svn:special set. */ 176static svn_error_t * 177copy_versioned_file(svn_wc__db_t *db, 178 const char *src_abspath, 179 const char *dst_abspath, 180 const char *dst_op_root_abspath, 181 const char *tmpdir_abspath, 182 svn_boolean_t metadata_only, 183 svn_boolean_t conflicted, 184 svn_boolean_t is_move, 185 svn_cancel_func_t cancel_func, 186 void *cancel_baton, 187 svn_wc_notify_func2_t notify_func, 188 void *notify_baton, 189 apr_pool_t *scratch_pool) 190{ 191 svn_skel_t *work_items = NULL; 192 193 /* In case we are copying from one WC to another (e.g. an external dir), 194 ensure the destination WC has a copy of the pristine text. */ 195 196 /* Prepare a temp copy of the filesystem node. It is usually a file, but 197 copy recursively if it's a dir. */ 198 if (!metadata_only) 199 { 200 const char *my_src_abspath = NULL; 201 svn_boolean_t handle_as_unversioned = FALSE; 202 203 /* By default, take the copy source as given. */ 204 my_src_abspath = src_abspath; 205 206 if (conflicted) 207 { 208 svn_skel_t *conflict; 209 const char *conflict_working; 210 svn_error_t *err; 211 212 /* Is there a text conflict at the source path? */ 213 SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, 214 scratch_pool, scratch_pool)); 215 216 err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL, 217 db, src_abspath, conflict, 218 scratch_pool, 219 scratch_pool); 220 221 if (err && err->apr_err == SVN_ERR_WC_MISSING) 222 { 223 /* not text conflicted */ 224 svn_error_clear(err); 225 conflict_working = NULL; 226 } 227 else 228 SVN_ERR(err); 229 230 if (conflict_working) 231 { 232 svn_node_kind_t working_kind; 233 234 /* Does the ".mine" file exist? */ 235 SVN_ERR(svn_io_check_path(conflict_working, &working_kind, 236 scratch_pool)); 237 238 if (working_kind == svn_node_file) 239 { 240 /* Don't perform unmodified/pristine optimization */ 241 handle_as_unversioned = TRUE; 242 my_src_abspath = conflict_working; 243 } 244 } 245 } 246 247 SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath, 248 dst_abspath, tmpdir_abspath, 249 TRUE /* file_copy */, 250 handle_as_unversioned /* unversioned */, 251 cancel_func, cancel_baton, 252 scratch_pool, scratch_pool)); 253 } 254 255 /* Copy the (single) node's metadata, and move the new filesystem node 256 into place. */ 257 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, 258 dst_op_root_abspath, is_move, work_items, 259 scratch_pool)); 260 261 if (notify_func) 262 { 263 svn_wc_notify_t *notify 264 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, 265 scratch_pool); 266 notify->kind = svn_node_file; 267 268 /* When we notify that we performed a copy, make sure we already did */ 269 if (work_items != NULL) 270 SVN_ERR(svn_wc__wq_run(db, dst_abspath, 271 cancel_func, cancel_baton, scratch_pool)); 272 (*notify_func)(notify_baton, notify, scratch_pool); 273 } 274 return SVN_NO_ERROR; 275} 276 277/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB, 278 recursively. If METADATA_ONLY is true, copy only the versioned metadata, 279 otherwise copy both the versioned metadata and the filesystem nodes (even 280 if they are the wrong kind, and including unversioned children). 281 If IS_MOVE is true, record move information in working copy meta 282 data in addition to copying the directory. 283 284 WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root) 285 */ 286static svn_error_t * 287copy_versioned_dir(svn_wc__db_t *db, 288 const char *src_abspath, 289 const char *dst_abspath, 290 const char *dst_op_root_abspath, 291 const char *tmpdir_abspath, 292 svn_boolean_t metadata_only, 293 svn_boolean_t is_move, 294 svn_cancel_func_t cancel_func, 295 void *cancel_baton, 296 svn_wc_notify_func2_t notify_func, 297 void *notify_baton, 298 apr_pool_t *scratch_pool) 299{ 300 svn_skel_t *work_items = NULL; 301 const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); 302 apr_hash_t *versioned_children; 303 apr_hash_t *conflicted_children; 304 apr_hash_t *disk_children; 305 apr_hash_index_t *hi; 306 svn_node_kind_t disk_kind; 307 apr_pool_t *iterpool; 308 309 /* Prepare a temp copy of the single filesystem node (usually a dir). */ 310 if (!metadata_only) 311 { 312 SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind, 313 db, src_abspath, dst_abspath, 314 tmpdir_abspath, 315 FALSE /* file_copy */, 316 FALSE /* unversioned */, 317 cancel_func, cancel_baton, 318 scratch_pool, scratch_pool)); 319 } 320 321 /* Copy the (single) node's metadata, and move the new filesystem node 322 into place. */ 323 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, 324 dst_op_root_abspath, is_move, work_items, 325 scratch_pool)); 326 327 if (notify_func) 328 { 329 svn_wc_notify_t *notify 330 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, 331 scratch_pool); 332 notify->kind = svn_node_dir; 333 334 /* When we notify that we performed a copy, make sure we already did */ 335 if (work_items != NULL) 336 SVN_ERR(svn_wc__wq_run(db, dir_abspath, 337 cancel_func, cancel_baton, scratch_pool)); 338 339 (*notify_func)(notify_baton, notify, scratch_pool); 340 } 341 342 if (!metadata_only && disk_kind == svn_node_dir) 343 /* All filesystem children, versioned and unversioned. We're only 344 interested in their names, so we can pass TRUE as the only_check_type 345 param. */ 346 SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE, 347 scratch_pool, scratch_pool)); 348 else 349 disk_children = NULL; 350 351 /* Copy all the versioned children */ 352 iterpool = svn_pool_create(scratch_pool); 353 SVN_ERR(svn_wc__db_read_children_info(&versioned_children, 354 &conflicted_children, 355 db, src_abspath, 356 scratch_pool, iterpool)); 357 for (hi = apr_hash_first(scratch_pool, versioned_children); 358 hi; 359 hi = apr_hash_next(hi)) 360 { 361 const char *child_name, *child_src_abspath, *child_dst_abspath; 362 struct svn_wc__db_info_t *info; 363 364 svn_pool_clear(iterpool); 365 366 if (cancel_func) 367 SVN_ERR(cancel_func(cancel_baton)); 368 369 child_name = svn__apr_hash_index_key(hi); 370 info = svn__apr_hash_index_val(hi); 371 child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool); 372 child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool); 373 374 if (info->op_root) 375 SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db, 376 child_src_abspath, 377 child_dst_abspath, 378 is_move, 379 scratch_pool)); 380 381 if (info->status == svn_wc__db_status_normal 382 || info->status == svn_wc__db_status_added) 383 { 384 /* We have more work to do than just changing the DB */ 385 if (info->kind == svn_node_file) 386 { 387 /* We should skip this node if this child is a file external 388 (issues #3589, #4000) */ 389 if (!info->file_external) 390 SVN_ERR(copy_versioned_file(db, 391 child_src_abspath, 392 child_dst_abspath, 393 dst_op_root_abspath, 394 tmpdir_abspath, 395 metadata_only, info->conflicted, 396 is_move, 397 cancel_func, cancel_baton, 398 NULL, NULL, 399 iterpool)); 400 } 401 else if (info->kind == svn_node_dir) 402 SVN_ERR(copy_versioned_dir(db, 403 child_src_abspath, child_dst_abspath, 404 dst_op_root_abspath, tmpdir_abspath, 405 metadata_only, is_move, 406 cancel_func, cancel_baton, NULL, NULL, 407 iterpool)); 408 else 409 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 410 _("cannot handle node kind for '%s'"), 411 svn_dirent_local_style(child_src_abspath, 412 scratch_pool)); 413 } 414 else if (info->status == svn_wc__db_status_deleted 415 || info->status == svn_wc__db_status_not_present 416 || info->status == svn_wc__db_status_excluded) 417 { 418 /* This will be copied as some kind of deletion. Don't touch 419 any actual files */ 420 SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, 421 child_dst_abspath, dst_op_root_abspath, 422 is_move, NULL, iterpool)); 423 424 /* Don't recurse on children while all we do is creating not-present 425 children */ 426 } 427 else if (info->status == svn_wc__db_status_incomplete) 428 { 429 /* Should go ahead and copy incomplete to incomplete? Try to 430 copy as much as possible, or give up early? */ 431 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 432 _("Cannot handle status of '%s'"), 433 svn_dirent_local_style(child_src_abspath, 434 iterpool)); 435 } 436 else 437 { 438 SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded); 439 440 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 441 _("Cannot copy '%s' excluded by server"), 442 svn_dirent_local_style(child_src_abspath, 443 iterpool)); 444 } 445 446 if (disk_children 447 && (info->status == svn_wc__db_status_normal 448 || info->status == svn_wc__db_status_added)) 449 { 450 /* Remove versioned child as it has been handled */ 451 svn_hash_sets(disk_children, child_name, NULL); 452 } 453 } 454 455 /* Copy the remaining filesystem children, which are unversioned, skipping 456 any conflict-marker files. */ 457 if (disk_children && apr_hash_count(disk_children)) 458 { 459 apr_hash_t *marker_files; 460 461 SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db, 462 src_abspath, scratch_pool, 463 scratch_pool)); 464 465 work_items = NULL; 466 467 for (hi = apr_hash_first(scratch_pool, disk_children); hi; 468 hi = apr_hash_next(hi)) 469 { 470 const char *name = svn__apr_hash_index_key(hi); 471 const char *unver_src_abspath, *unver_dst_abspath; 472 svn_skel_t *work_item; 473 474 if (svn_wc_is_adm_dir(name, iterpool)) 475 continue; 476 477 if (cancel_func) 478 SVN_ERR(cancel_func(cancel_baton)); 479 480 svn_pool_clear(iterpool); 481 unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool); 482 unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool); 483 484 if (marker_files && 485 svn_hash_gets(marker_files, unver_src_abspath)) 486 continue; 487 488 SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath, 489 unver_dst_abspath, tmpdir_abspath, 490 TRUE /* recursive */, TRUE /* unversioned */, 491 cancel_func, cancel_baton, 492 scratch_pool, iterpool)); 493 494 if (work_item) 495 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); 496 } 497 SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool)); 498 } 499 500 svn_pool_destroy(iterpool); 501 502 return SVN_NO_ERROR; 503} 504 505 506/* The guts of svn_wc_copy3() and svn_wc_move(). 507 * The additional parameter IS_MOVE indicates whether this is a copy or 508 * a move operation. 509 * 510 * If MOVE_DEGRADED_TO_COPY is not NULL and a move had to be degraded 511 * to a copy, then set *MOVE_DEGRADED_TO_COPY. */ 512static svn_error_t * 513copy_or_move(svn_boolean_t *move_degraded_to_copy, 514 svn_wc_context_t *wc_ctx, 515 const char *src_abspath, 516 const char *dst_abspath, 517 svn_boolean_t metadata_only, 518 svn_boolean_t is_move, 519 svn_boolean_t allow_mixed_revisions, 520 svn_cancel_func_t cancel_func, 521 void *cancel_baton, 522 svn_wc_notify_func2_t notify_func, 523 void *notify_baton, 524 apr_pool_t *scratch_pool) 525{ 526 svn_wc__db_t *db = wc_ctx->db; 527 svn_node_kind_t src_db_kind; 528 const char *dstdir_abspath; 529 svn_boolean_t conflicted; 530 const char *tmpdir_abspath; 531 const char *src_wcroot_abspath; 532 const char *dst_wcroot_abspath; 533 svn_boolean_t within_one_wc; 534 svn_wc__db_status_t src_status; 535 svn_error_t *err; 536 537 SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); 538 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 539 540 dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); 541 542 /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH; 543 throw an error if not. */ 544 { 545 svn_wc__db_status_t dstdir_status; 546 const char *src_repos_root_url, *dst_repos_root_url; 547 const char *src_repos_uuid, *dst_repos_uuid; 548 const char *src_repos_relpath; 549 550 err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL, 551 &src_repos_relpath, &src_repos_root_url, 552 &src_repos_uuid, NULL, NULL, NULL, NULL, NULL, 553 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 554 NULL, &conflicted, NULL, NULL, NULL, NULL, 555 NULL, NULL, 556 db, src_abspath, scratch_pool, scratch_pool); 557 558 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 559 { 560 /* Replicate old error code and text */ 561 svn_error_clear(err); 562 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, 563 _("'%s' is not under version control"), 564 svn_dirent_local_style(src_abspath, 565 scratch_pool)); 566 } 567 else 568 SVN_ERR(err); 569 570 /* Do this now, as we know the right data is cached */ 571 SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath, 572 scratch_pool, scratch_pool)); 573 574 switch (src_status) 575 { 576 case svn_wc__db_status_deleted: 577 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 578 _("Deleted node '%s' can't be copied."), 579 svn_dirent_local_style(src_abspath, 580 scratch_pool)); 581 582 case svn_wc__db_status_excluded: 583 case svn_wc__db_status_server_excluded: 584 case svn_wc__db_status_not_present: 585 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 586 _("The node '%s' was not found."), 587 svn_dirent_local_style(src_abspath, 588 scratch_pool)); 589 default: 590 break; 591 } 592 593 if (is_move && ! strcmp(src_abspath, src_wcroot_abspath)) 594 { 595 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 596 _("'%s' is the root of a working copy and " 597 "cannot be moved"), 598 svn_dirent_local_style(src_abspath, 599 scratch_pool)); 600 } 601 if (is_move && src_repos_relpath && !src_repos_relpath[0]) 602 { 603 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 604 _("'%s' represents the repository root " 605 "and cannot be moved"), 606 svn_dirent_local_style(src_abspath, 607 scratch_pool)); 608 } 609 610 err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL, 611 &dst_repos_root_url, &dst_repos_uuid, NULL, 612 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 613 NULL, NULL, NULL, NULL, NULL, NULL, 614 NULL, NULL, NULL, NULL, 615 NULL, NULL, NULL, 616 db, dstdir_abspath, 617 scratch_pool, scratch_pool); 618 619 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 620 { 621 /* An unversioned destination directory exists on disk. */ 622 svn_error_clear(err); 623 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, 624 _("'%s' is not under version control"), 625 svn_dirent_local_style(dstdir_abspath, 626 scratch_pool)); 627 } 628 else 629 SVN_ERR(err); 630 631 /* Do this now, as we know the right data is cached */ 632 SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath, 633 scratch_pool, scratch_pool)); 634 635 if (!src_repos_root_url) 636 { 637 if (src_status == svn_wc__db_status_added) 638 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, 639 &src_repos_root_url, 640 &src_repos_uuid, NULL, NULL, NULL, 641 NULL, 642 db, src_abspath, 643 scratch_pool, scratch_pool)); 644 else 645 /* If not added, the node must have a base or we can't copy */ 646 SVN_ERR(svn_wc__db_scan_base_repos(NULL, &src_repos_root_url, 647 &src_repos_uuid, 648 db, src_abspath, 649 scratch_pool, scratch_pool)); 650 } 651 652 if (!dst_repos_root_url) 653 { 654 if (dstdir_status == svn_wc__db_status_added) 655 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, 656 &dst_repos_root_url, 657 &dst_repos_uuid, NULL, NULL, NULL, 658 NULL, 659 db, dstdir_abspath, 660 scratch_pool, scratch_pool)); 661 else 662 /* If not added, the node must have a base or we can't copy */ 663 SVN_ERR(svn_wc__db_scan_base_repos(NULL, &dst_repos_root_url, 664 &dst_repos_uuid, 665 db, dstdir_abspath, 666 scratch_pool, scratch_pool)); 667 } 668 669 if (strcmp(src_repos_root_url, dst_repos_root_url) != 0 670 || strcmp(src_repos_uuid, dst_repos_uuid) != 0) 671 return svn_error_createf( 672 SVN_ERR_WC_INVALID_SCHEDULE, NULL, 673 _("Cannot copy to '%s', as it is not from repository '%s'; " 674 "it is from '%s'"), 675 svn_dirent_local_style(dst_abspath, scratch_pool), 676 src_repos_root_url, dst_repos_root_url); 677 678 if (dstdir_status == svn_wc__db_status_deleted) 679 return svn_error_createf( 680 SVN_ERR_WC_INVALID_SCHEDULE, NULL, 681 _("Cannot copy to '%s' as it is scheduled for deletion"), 682 svn_dirent_local_style(dst_abspath, scratch_pool)); 683 /* ### should report dstdir_abspath instead of dst_abspath? */ 684 } 685 686 /* TODO(#2843): Rework the error report. */ 687 /* Check if the copy target is missing or hidden and thus not exist on the 688 disk, before actually doing the file copy. */ 689 { 690 svn_wc__db_status_t dst_status; 691 692 err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, 693 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 694 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 695 NULL, NULL, NULL, NULL, NULL, 696 db, dst_abspath, scratch_pool, scratch_pool); 697 698 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 699 return svn_error_trace(err); 700 701 svn_error_clear(err); 702 703 if (!err) 704 switch (dst_status) 705 { 706 case svn_wc__db_status_excluded: 707 return svn_error_createf( 708 SVN_ERR_ENTRY_EXISTS, NULL, 709 _("'%s' is already under version control " 710 "but is excluded."), 711 svn_dirent_local_style(dst_abspath, scratch_pool)); 712 case svn_wc__db_status_server_excluded: 713 return svn_error_createf( 714 SVN_ERR_ENTRY_EXISTS, NULL, 715 _("'%s' is already under version control"), 716 svn_dirent_local_style(dst_abspath, scratch_pool)); 717 718 case svn_wc__db_status_deleted: 719 case svn_wc__db_status_not_present: 720 break; /* OK to add */ 721 722 default: 723 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, 724 _("There is already a versioned item '%s'"), 725 svn_dirent_local_style(dst_abspath, 726 scratch_pool)); 727 } 728 } 729 730 /* Check that the target path is not obstructed, if required. */ 731 if (!metadata_only) 732 { 733 svn_node_kind_t dst_kind; 734 735 /* (We need only to check the root of the copy, not every path inside 736 copy_versioned_file/_dir.) */ 737 SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool)); 738 if (dst_kind != svn_node_none) 739 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, 740 _("'%s' already exists and is in the way"), 741 svn_dirent_local_style(dst_abspath, 742 scratch_pool)); 743 } 744 745 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db, 746 dstdir_abspath, 747 scratch_pool, scratch_pool)); 748 749 within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0); 750 751 if (is_move 752 && !within_one_wc) 753 { 754 if (move_degraded_to_copy) 755 *move_degraded_to_copy = TRUE; 756 757 is_move = FALSE; 758 } 759 760 if (!within_one_wc) 761 SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath, 762 cancel_func, cancel_baton, 763 scratch_pool)); 764 765 if (src_db_kind == svn_node_file 766 || src_db_kind == svn_node_symlink) 767 { 768 err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath, 769 tmpdir_abspath, 770 metadata_only, conflicted, is_move, 771 cancel_func, cancel_baton, 772 notify_func, notify_baton, 773 scratch_pool); 774 } 775 else 776 { 777 if (is_move 778 && src_status == svn_wc__db_status_normal) 779 { 780 svn_revnum_t min_rev; 781 svn_revnum_t max_rev; 782 783 /* Verify that the move source is a single-revision subtree. */ 784 SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db, 785 src_abspath, FALSE, scratch_pool)); 786 if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) && 787 min_rev != max_rev) 788 { 789 if (!allow_mixed_revisions) 790 return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL, 791 _("Cannot move mixed-revision " 792 "subtree '%s' [%ld:%ld]; " 793 "try updating it first"), 794 svn_dirent_local_style(src_abspath, 795 scratch_pool), 796 min_rev, max_rev); 797 798 is_move = FALSE; 799 if (move_degraded_to_copy) 800 *move_degraded_to_copy = TRUE; 801 } 802 } 803 804 err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath, 805 tmpdir_abspath, metadata_only, is_move, 806 cancel_func, cancel_baton, 807 notify_func, notify_baton, 808 scratch_pool); 809 } 810 811 if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED)) 812 return svn_error_trace(err); 813 814 if (is_move) 815 err = svn_error_compose_create(err, 816 svn_wc__db_op_handle_move_back(NULL, 817 db, dst_abspath, src_abspath, 818 NULL /* work_items */, 819 scratch_pool)); 820 821 /* Run the work queue with the remaining work */ 822 SVN_ERR(svn_error_compose_create( 823 err, 824 svn_wc__wq_run(db, dst_abspath, 825 cancel_func, cancel_baton, 826 scratch_pool))); 827 828 return SVN_NO_ERROR; 829} 830 831 832/* Public Interface */ 833 834svn_error_t * 835svn_wc_copy3(svn_wc_context_t *wc_ctx, 836 const char *src_abspath, 837 const char *dst_abspath, 838 svn_boolean_t metadata_only, 839 svn_cancel_func_t cancel_func, 840 void *cancel_baton, 841 svn_wc_notify_func2_t notify_func, 842 void *notify_baton, 843 apr_pool_t *scratch_pool) 844{ 845 /* Verify that we have the required write lock. */ 846 SVN_ERR(svn_wc__write_check(wc_ctx->db, 847 svn_dirent_dirname(dst_abspath, scratch_pool), 848 scratch_pool)); 849 850 return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath, 851 metadata_only, FALSE /* is_move */, 852 TRUE /* allow_mixed_revisions */, 853 cancel_func, cancel_baton, 854 notify_func, notify_baton, 855 scratch_pool)); 856} 857 858 859/* Remove the conflict markers of NODE_ABSPATH, that were left over after 860 copying NODE_ABSPATH from SRC_ABSPATH. 861 862 Only use this function when you know what you're doing. This function 863 explicitly ignores some case insensitivity issues! 864 865 */ 866static svn_error_t * 867remove_node_conflict_markers(svn_wc__db_t *db, 868 const char *src_abspath, 869 const char *node_abspath, 870 apr_pool_t *scratch_pool) 871{ 872 svn_skel_t *conflict; 873 874 SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, 875 scratch_pool, scratch_pool)); 876 877 /* Do we have conflict markers that should be removed? */ 878 if (conflict != NULL) 879 { 880 const apr_array_header_t *markers; 881 int i; 882 const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool); 883 const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool); 884 885 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath, 886 conflict, 887 scratch_pool, scratch_pool)); 888 889 /* No iterpool: Maximum number of possible conflict markers is 4 */ 890 for (i = 0; markers && (i < markers->nelts); i++) 891 { 892 const char *marker_abspath; 893 const char *child_relpath; 894 const char *child_abpath; 895 896 marker_abspath = APR_ARRAY_IDX(markers, i, const char *); 897 898 child_relpath = svn_dirent_is_child(src_dir, marker_abspath, NULL); 899 900 if (child_relpath) 901 { 902 child_abpath = svn_dirent_join(dst_dir, child_relpath, 903 scratch_pool); 904 905 SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, scratch_pool)); 906 } 907 } 908 } 909 910 return SVN_NO_ERROR; 911} 912 913/* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over 914 after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH. 915 916 This function doesn't remove the conflict markers on WC_DIR_ABSPATH 917 itself! 918 919 Only use this function when you know what you're doing. This function 920 explicitly ignores some case insensitivity issues! 921 */ 922static svn_error_t * 923remove_all_conflict_markers(svn_wc__db_t *db, 924 const char *src_dir_abspath, 925 const char *wc_dir_abspath, 926 apr_pool_t *scratch_pool) 927{ 928 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 929 apr_hash_t *nodes; 930 apr_hash_t *conflicts; /* Unused */ 931 apr_hash_index_t *hi; 932 933 /* Reuse a status helper to obtain all subdirs and conflicts in a single 934 db transaction. */ 935 /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy 936 artillery. */ 937 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, 938 src_dir_abspath, 939 scratch_pool, iterpool)); 940 941 for (hi = apr_hash_first(scratch_pool, nodes); 942 hi; 943 hi = apr_hash_next(hi)) 944 { 945 const char *name = svn__apr_hash_index_key(hi); 946 struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi); 947 948 if (info->conflicted) 949 { 950 svn_pool_clear(iterpool); 951 SVN_ERR(remove_node_conflict_markers( 952 db, 953 svn_dirent_join(src_dir_abspath, name, iterpool), 954 svn_dirent_join(wc_dir_abspath, name, iterpool), 955 iterpool)); 956 } 957 if (info->kind == svn_node_dir) 958 { 959 svn_pool_clear(iterpool); 960 SVN_ERR(remove_all_conflict_markers( 961 db, 962 svn_dirent_join(src_dir_abspath, name, iterpool), 963 svn_dirent_join(wc_dir_abspath, name, iterpool), 964 iterpool)); 965 } 966 } 967 968 svn_pool_destroy(iterpool); 969 return SVN_NO_ERROR; 970} 971 972svn_error_t * 973svn_wc__move2(svn_wc_context_t *wc_ctx, 974 const char *src_abspath, 975 const char *dst_abspath, 976 svn_boolean_t metadata_only, 977 svn_boolean_t allow_mixed_revisions, 978 svn_cancel_func_t cancel_func, 979 void *cancel_baton, 980 svn_wc_notify_func2_t notify_func, 981 void *notify_baton, 982 apr_pool_t *scratch_pool) 983{ 984 svn_wc__db_t *db = wc_ctx->db; 985 svn_boolean_t move_degraded_to_copy = FALSE; 986 svn_node_kind_t kind; 987 svn_boolean_t conflicted; 988 989 /* Verify that we have the required write locks. */ 990 SVN_ERR(svn_wc__write_check(wc_ctx->db, 991 svn_dirent_dirname(src_abspath, scratch_pool), 992 scratch_pool)); 993 SVN_ERR(svn_wc__write_check(wc_ctx->db, 994 svn_dirent_dirname(dst_abspath, scratch_pool), 995 scratch_pool)); 996 997 SVN_ERR(copy_or_move(&move_degraded_to_copy, 998 wc_ctx, src_abspath, dst_abspath, 999 TRUE /* metadata_only */, 1000 TRUE /* is_move */, 1001 allow_mixed_revisions, 1002 cancel_func, cancel_baton, 1003 notify_func, notify_baton, 1004 scratch_pool)); 1005 1006 /* An interrupt at this point will leave the new copy marked as 1007 moved-here but the source has not yet been deleted or marked as 1008 moved-to. */ 1009 1010 /* Should we be using a workqueue for this move? It's not clear. 1011 What should happen if the copy above is interrupted? The user 1012 may want to abort the move and a workqueue might interfere with 1013 that. 1014 1015 BH: On Windows it is not unlikely to encounter an access denied on 1016 this line. Installing the move in the workqueue via the copy_or_move 1017 might make it hard to recover from that situation, while the DB 1018 is still in a valid state. So be careful when switching this over 1019 to the workqueue. */ 1020 if (!metadata_only) 1021 SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool)); 1022 1023 SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, 1024 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1025 NULL, NULL, NULL, NULL, NULL, NULL, 1026 &conflicted, NULL, NULL, NULL, 1027 NULL, NULL, NULL, 1028 db, src_abspath, 1029 scratch_pool, scratch_pool)); 1030 1031 if (kind == svn_node_dir) 1032 SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath, 1033 scratch_pool)); 1034 1035 if (conflicted) 1036 SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath, 1037 scratch_pool)); 1038 1039 SVN_ERR(svn_wc__db_op_delete(db, src_abspath, 1040 move_degraded_to_copy ? NULL : dst_abspath, 1041 TRUE /* delete_dir_externals */, 1042 NULL /* conflict */, NULL /* work_items */, 1043 cancel_func, cancel_baton, 1044 notify_func, notify_baton, 1045 scratch_pool)); 1046 1047 return SVN_NO_ERROR; 1048} 1049