copy.c revision 289166
1299425Smm/* 2299425Smm * copy.c: wc 'copy' functionality. 3299425Smm * 4299425Smm * ==================================================================== 5299425Smm * Licensed to the Apache Software Foundation (ASF) under one 6299425Smm * or more contributor license agreements. See the NOTICE file 7299425Smm * distributed with this work for additional information 8299425Smm * regarding copyright ownership. The ASF licenses this file 9299425Smm * to you under the Apache License, Version 2.0 (the 10299425Smm * "License"); you may not use this file except in compliance 11299425Smm * with the License. You may obtain a copy of the License at 12299425Smm * 13299425Smm * http://www.apache.org/licenses/LICENSE-2.0 14299425Smm * 15299425Smm * Unless required by applicable law or agreed to in writing, 16299425Smm * software distributed under the License is distributed on an 17299425Smm * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18299425Smm * KIND, either express or implied. See the License for the 19299425Smm * specific language governing permissions and limitations 20299425Smm * under the License. 21299425Smm * ==================================================================== 22299425Smm */ 23299425Smm 24299425Smm/* ==================================================================== */ 25299425Smm 26299425Smm 27299425Smm 28299425Smm/*** Includes. ***/ 29299425Smm 30299425Smm#include <string.h> 31299425Smm#include "svn_pools.h" 32299425Smm#include "svn_error.h" 33299425Smm#include "svn_dirent_uri.h" 34299425Smm#include "svn_path.h" 35299425Smm#include "svn_hash.h" 36299425Smm 37299425Smm#include "wc.h" 38299425Smm#include "workqueue.h" 39299425Smm#include "props.h" 40299425Smm#include "conflicts.h" 41299425Smm 42299425Smm#include "svn_private_config.h" 43299425Smm#include "private/svn_wc_private.h" 44299425Smm 45299425Smm 46299425Smm/*** Code. ***/ 47299425Smm 48299425Smm/* Make a copy of the filesystem node (or tree if RECURSIVE) at 49299425Smm SRC_ABSPATH under a temporary name in the directory 50299425Smm TMPDIR_ABSPATH and return the absolute path of the copy in 51299425Smm *DST_ABSPATH. Return the node kind of SRC_ABSPATH in *KIND. If 52299425Smm SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate 53299425Smm that no copy was made. */ 54299425Smmstatic svn_error_t * 55299425Smmcopy_to_tmpdir(svn_skel_t **work_item, 56299425Smm svn_node_kind_t *kind, 57299425Smm svn_wc__db_t *db, 58299425Smm const char *src_abspath, 59299425Smm const char *dst_abspath, 60299425Smm const char *tmpdir_abspath, 61299425Smm svn_boolean_t file_copy, 62299425Smm svn_boolean_t unversioned, 63299425Smm svn_cancel_func_t cancel_func, 64299425Smm void *cancel_baton, 65299425Smm apr_pool_t *result_pool, 66299425Smm apr_pool_t *scratch_pool) 67299425Smm{ 68299425Smm svn_boolean_t is_special; 69299425Smm svn_io_file_del_t delete_when; 70299425Smm const char *dst_tmp_abspath; 71299425Smm svn_node_kind_t dsk_kind; 72299425Smm if (!kind) 73299425Smm kind = &dsk_kind; 74299425Smm 75299425Smm *work_item = NULL; 76299425Smm 77299425Smm SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special, 78299425Smm scratch_pool)); 79299425Smm if (*kind == svn_node_none) 80299425Smm { 81299425Smm return SVN_NO_ERROR; 82299425Smm } 83299425Smm else if (*kind == svn_node_unknown) 84299425Smm { 85299425Smm return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 86299425Smm _("Source '%s' is unexpected kind"), 87299425Smm svn_dirent_local_style(src_abspath, 88299425Smm scratch_pool)); 89299425Smm } 90299425Smm else if (*kind == svn_node_dir || is_special) 91299425Smm delete_when = svn_io_file_del_on_close; 92299425Smm else /* the default case: (*kind == svn_node_file) */ 93299425Smm delete_when = svn_io_file_del_none; 94299425Smm 95299425Smm /* ### Do we need a pool cleanup to remove the copy? We can't use 96299425Smm ### svn_io_file_del_on_pool_cleanup above because a) it won't 97299425Smm ### handle the directory case and b) we need to be able to remove 98299425Smm ### the cleanup before queueing the move work item. */ 99299425Smm 100299425Smm if (file_copy && !unversioned) 101299425Smm { 102299425Smm svn_boolean_t modified; 103299425Smm /* It's faster to look for mods on the source now, as 104299425Smm the timestamp might match, than to examine the 105299425Smm destination later as the destination timestamp will 106299425Smm never match. */ 107299425Smm SVN_ERR(svn_wc__internal_file_modified_p(&modified, 108299425Smm db, src_abspath, 109299425Smm FALSE, scratch_pool)); 110299425Smm if (!modified) 111299425Smm { 112299425Smm /* Why create a temp copy if we can just reinstall from pristine? */ 113299425Smm SVN_ERR(svn_wc__wq_build_file_install(work_item, 114299425Smm db, dst_abspath, NULL, FALSE, 115299425Smm TRUE, 116299425Smm result_pool, scratch_pool)); 117299425Smm return SVN_NO_ERROR; 118299425Smm } 119299425Smm } 120299425Smm 121299425Smm /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave 122299425Smm a file there and then overwrite it; otherwise leave no node on disk at 123299425Smm that path. In the latter case, something else might use that path 124299425Smm before we get around to using it a moment later, but never mind. */ 125299425Smm SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath, 126299425Smm delete_when, scratch_pool, scratch_pool)); 127299425Smm 128299425Smm if (*kind == svn_node_dir) 129299425Smm { 130299425Smm if (file_copy) 131299425Smm SVN_ERR(svn_io_copy_dir_recursively( 132299425Smm src_abspath, 133299425Smm tmpdir_abspath, 134299425Smm svn_dirent_basename(dst_tmp_abspath, scratch_pool), 135299425Smm TRUE, /* copy_perms */ 136299425Smm cancel_func, cancel_baton, 137299425Smm scratch_pool)); 138299425Smm else 139299425Smm SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool)); 140299425Smm } 141299425Smm else if (!is_special) 142299425Smm SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath, 143299425Smm TRUE /* copy_perms */, 144299425Smm scratch_pool)); 145299425Smm else 146299425Smm SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool)); 147299425Smm 148299425Smm if (file_copy) 149299425Smm { 150299425Smm /* Remove 'read-only' from the destination file; it's a local add now. */ 151299425Smm SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath, 152299425Smm FALSE, scratch_pool)); 153299425Smm } 154299425Smm 155299425Smm SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath, 156299425Smm dst_tmp_abspath, dst_abspath, 157299425Smm result_pool, scratch_pool)); 158299425Smm 159299425Smm return SVN_NO_ERROR; 160299425Smm} 161299425Smm 162299425Smm/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB. 163299425Smm If METADATA_ONLY is true, copy only the versioned metadata, 164299425Smm otherwise copy both the versioned metadata and the filesystem node (even 165299425Smm if it is the wrong kind, and recursively if it is a dir). 166299425Smm 167299425Smm If IS_MOVE is true, record move information in working copy meta 168299425Smm data in addition to copying the file. 169299425Smm 170299425Smm If the versioned file has a text conflict, and the .mine file exists in 171299425Smm the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the 172299425Smm versioned file itself. 173299425Smm 174299425Smm This also works for versioned symlinks that are stored in the db as 175299425Smm svn_node_file with svn:special set. */ 176299425Smmstatic svn_error_t * 177299425Smmcopy_versioned_file(svn_wc__db_t *db, 178299425Smm const char *src_abspath, 179299425Smm const char *dst_abspath, 180299425Smm const char *dst_op_root_abspath, 181299425Smm const char *tmpdir_abspath, 182299425Smm svn_boolean_t metadata_only, 183299425Smm svn_boolean_t conflicted, 184299425Smm svn_boolean_t is_move, 185299425Smm svn_cancel_func_t cancel_func, 186299425Smm void *cancel_baton, 187299425Smm svn_wc_notify_func2_t notify_func, 188299425Smm void *notify_baton, 189299425Smm apr_pool_t *scratch_pool) 190299425Smm{ 191299425Smm svn_skel_t *work_items = NULL; 192299425Smm 193299425Smm /* In case we are copying from one WC to another (e.g. an external dir), 194299425Smm ensure the destination WC has a copy of the pristine text. */ 195299425Smm 196299425Smm /* Prepare a temp copy of the filesystem node. It is usually a file, but 197299425Smm copy recursively if it's a dir. */ 198299425Smm if (!metadata_only) 199299425Smm { 200299425Smm const char *my_src_abspath = NULL; 201299425Smm svn_boolean_t handle_as_unversioned = FALSE; 202299425Smm 203299425Smm /* By default, take the copy source as given. */ 204299425Smm my_src_abspath = src_abspath; 205299425Smm 206299425Smm if (conflicted) 207299425Smm { 208299425Smm svn_skel_t *conflict; 209299425Smm const char *conflict_working; 210299425Smm svn_error_t *err; 211299425Smm 212299425Smm /* Is there a text conflict at the source path? */ 213299425Smm SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, 214299425Smm scratch_pool, scratch_pool)); 215299425Smm 216299425Smm err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL, 217299425Smm db, src_abspath, conflict, 218299425Smm scratch_pool, 219299425Smm scratch_pool); 220299425Smm 221299425Smm if (err && err->apr_err == SVN_ERR_WC_MISSING) 222299425Smm { 223299425Smm /* not text conflicted */ 224299425Smm svn_error_clear(err); 225299425Smm conflict_working = NULL; 226299425Smm } 227316338Smm else 228299425Smm SVN_ERR(err); 229299425Smm 230299425Smm if (conflict_working) 231299425Smm { 232299425Smm svn_node_kind_t working_kind; 233299425Smm 234299425Smm /* Does the ".mine" file exist? */ 235299425Smm SVN_ERR(svn_io_check_path(conflict_working, &working_kind, 236299425Smm scratch_pool)); 237299425Smm 238299425Smm if (working_kind == svn_node_file) 239299425Smm { 240299425Smm /* Don't perform unmodified/pristine optimization */ 241299425Smm handle_as_unversioned = TRUE; 242299425Smm my_src_abspath = conflict_working; 243299425Smm } 244299425Smm } 245299425Smm } 246299425Smm 247299425Smm SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath, 248299425Smm dst_abspath, tmpdir_abspath, 249299425Smm TRUE /* file_copy */, 250299425Smm handle_as_unversioned /* unversioned */, 251299425Smm cancel_func, cancel_baton, 252299425Smm scratch_pool, scratch_pool)); 253299425Smm } 254299425Smm 255299425Smm /* Copy the (single) node's metadata, and move the new filesystem node 256299425Smm into place. */ 257299425Smm SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, 258299425Smm dst_op_root_abspath, is_move, work_items, 259299425Smm scratch_pool)); 260299425Smm 261299425Smm if (notify_func) 262299425Smm { 263299425Smm svn_wc_notify_t *notify 264299425Smm = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, 265299425Smm scratch_pool); 266299425Smm notify->kind = svn_node_file; 267299425Smm 268299425Smm /* When we notify that we performed a copy, make sure we already did */ 269299425Smm if (work_items != NULL) 270299425Smm SVN_ERR(svn_wc__wq_run(db, dst_abspath, 271299425Smm cancel_func, cancel_baton, scratch_pool)); 272299425Smm (*notify_func)(notify_baton, notify, scratch_pool); 273299425Smm } 274299425Smm return SVN_NO_ERROR; 275299425Smm} 276299425Smm 277299425Smm/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB, 278299425Smm recursively. If METADATA_ONLY is true, copy only the versioned metadata, 279299425Smm otherwise copy both the versioned metadata and the filesystem nodes (even 280299425Smm if they are the wrong kind, and including unversioned children). 281299425Smm If IS_MOVE is true, record move information in working copy meta 282299425Smm data in addition to copying the directory. 283299425Smm 284299425Smm WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root) 285299425Smm */ 286299425Smmstatic svn_error_t * 287299425Smmcopy_versioned_dir(svn_wc__db_t *db, 288299425Smm const char *src_abspath, 289299425Smm const char *dst_abspath, 290299425Smm const char *dst_op_root_abspath, 291299425Smm const char *tmpdir_abspath, 292299425Smm svn_boolean_t metadata_only, 293299425Smm svn_boolean_t is_move, 294299425Smm svn_cancel_func_t cancel_func, 295299425Smm void *cancel_baton, 296299425Smm svn_wc_notify_func2_t notify_func, 297299425Smm void *notify_baton, 298299425Smm apr_pool_t *scratch_pool) 299299425Smm{ 300299425Smm svn_skel_t *work_items = NULL; 301299425Smm const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); 302299425Smm apr_hash_t *versioned_children; 303299425Smm apr_hash_t *conflicted_children; 304299425Smm apr_hash_t *disk_children; 305299425Smm apr_hash_index_t *hi; 306299425Smm svn_node_kind_t disk_kind; 307299425Smm apr_pool_t *iterpool; 308299425Smm 309299425Smm /* Prepare a temp copy of the single filesystem node (usually a dir). */ 310299425Smm if (!metadata_only) 311299425Smm { 312299425Smm SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind, 313299425Smm db, src_abspath, dst_abspath, 314299425Smm tmpdir_abspath, 315299425Smm FALSE /* file_copy */, 316299425Smm FALSE /* unversioned */, 317299425Smm cancel_func, cancel_baton, 318299425Smm scratch_pool, scratch_pool)); 319299425Smm } 320299425Smm 321299425Smm /* Copy the (single) node's metadata, and move the new filesystem node 322299425Smm into place. */ 323299425Smm SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, 324299425Smm dst_op_root_abspath, is_move, work_items, 325299425Smm scratch_pool)); 326299425Smm 327299425Smm if (notify_func) 328299425Smm { 329299425Smm svn_wc_notify_t *notify 330299425Smm = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, 331299425Smm scratch_pool); 332299425Smm notify->kind = svn_node_dir; 333299425Smm 334299425Smm /* When we notify that we performed a copy, make sure we already did */ 335299425Smm if (work_items != NULL) 336299425Smm SVN_ERR(svn_wc__wq_run(db, dir_abspath, 337299425Smm cancel_func, cancel_baton, scratch_pool)); 338299425Smm 339299425Smm (*notify_func)(notify_baton, notify, scratch_pool); 340299425Smm } 341358090Smm 342299425Smm if (!metadata_only && disk_kind == svn_node_dir) 343299425Smm /* All filesystem children, versioned and unversioned. We're only 344299425Smm interested in their names, so we can pass TRUE as the only_check_type 345299425Smm param. */ 346299425Smm SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE, 347299425Smm scratch_pool, scratch_pool)); 348299425Smm else 349299425Smm disk_children = NULL; 350299425Smm 351299425Smm /* Copy all the versioned children */ 352299425Smm iterpool = svn_pool_create(scratch_pool); 353299425Smm SVN_ERR(svn_wc__db_read_children_info(&versioned_children, 354299425Smm &conflicted_children, 355299425Smm db, src_abspath, 356299425Smm scratch_pool, iterpool)); 357299425Smm for (hi = apr_hash_first(scratch_pool, versioned_children); 358299425Smm hi; 359299425Smm hi = apr_hash_next(hi)) 360299425Smm { 361299425Smm const char *child_name, *child_src_abspath, *child_dst_abspath; 362299425Smm struct svn_wc__db_info_t *info; 363299425Smm 364358090Smm svn_pool_clear(iterpool); 365299425Smm 366299425Smm if (cancel_func) 367299425Smm SVN_ERR(cancel_func(cancel_baton)); 368299425Smm 369299425Smm child_name = svn__apr_hash_index_key(hi); 370299425Smm info = svn__apr_hash_index_val(hi); 371299425Smm child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool); 372299425Smm child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool); 373299425Smm 374299425Smm if (info->op_root) 375299425Smm SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db, 376299425Smm child_src_abspath, 377299425Smm child_dst_abspath, 378299425Smm is_move, 379299425Smm scratch_pool)); 380299425Smm 381299425Smm if (info->status == svn_wc__db_status_normal 382299425Smm || info->status == svn_wc__db_status_added) 383299425Smm { 384299425Smm /* We have more work to do than just changing the DB */ 385299425Smm if (info->kind == svn_node_file) 386299425Smm { 387299425Smm /* We should skip this node if this child is a file external 388299425Smm (issues #3589, #4000) */ 389299425Smm if (!info->file_external) 390299425Smm SVN_ERR(copy_versioned_file(db, 391299425Smm child_src_abspath, 392299425Smm child_dst_abspath, 393299425Smm dst_op_root_abspath, 394299425Smm tmpdir_abspath, 395299425Smm metadata_only, info->conflicted, 396299425Smm is_move, 397299425Smm cancel_func, cancel_baton, 398299425Smm NULL, NULL, 399299425Smm iterpool)); 400299425Smm } 401299425Smm else if (info->kind == svn_node_dir) 402299425Smm SVN_ERR(copy_versioned_dir(db, 403299425Smm child_src_abspath, child_dst_abspath, 404299425Smm dst_op_root_abspath, tmpdir_abspath, 405299425Smm metadata_only, is_move, 406299425Smm cancel_func, cancel_baton, NULL, NULL, 407299425Smm iterpool)); 408299425Smm else 409299425Smm return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 410299425Smm _("cannot handle node kind for '%s'"), 411299425Smm svn_dirent_local_style(child_src_abspath, 412299425Smm scratch_pool)); 413299425Smm } 414299425Smm else if (info->status == svn_wc__db_status_deleted 415299425Smm || info->status == svn_wc__db_status_not_present 416299425Smm || info->status == svn_wc__db_status_excluded) 417299425Smm { 418299425Smm /* This will be copied as some kind of deletion. Don't touch 419299425Smm any actual files */ 420299425Smm SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, 421299425Smm child_dst_abspath, dst_op_root_abspath, 422299425Smm is_move, NULL, iterpool)); 423299425Smm 424299425Smm /* Don't recurse on children while all we do is creating not-present 425299425Smm children */ 426299425Smm } 427299425Smm else if (info->status == svn_wc__db_status_incomplete) 428299425Smm { 429299425Smm /* Should go ahead and copy incomplete to incomplete? Try to 430299425Smm copy as much as possible, or give up early? */ 431299425Smm return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 432299425Smm _("Cannot handle status of '%s'"), 433299425Smm svn_dirent_local_style(child_src_abspath, 434299425Smm iterpool)); 435299425Smm } 436299425Smm else 437299425Smm { 438299425Smm SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded); 439299425Smm 440299425Smm return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 441299425Smm _("Cannot copy '%s' excluded by server"), 442299425Smm svn_dirent_local_style(child_src_abspath, 443299425Smm iterpool)); 444299425Smm } 445299425Smm 446299425Smm if (disk_children 447299425Smm && (info->status == svn_wc__db_status_normal 448299425Smm || info->status == svn_wc__db_status_added)) 449299425Smm { 450299425Smm /* Remove versioned child as it has been handled */ 451299425Smm svn_hash_sets(disk_children, child_name, NULL); 452299425Smm } 453299425Smm } 454299425Smm 455299425Smm /* Copy the remaining filesystem children, which are unversioned, skipping 456299425Smm any conflict-marker files. */ 457299425Smm if (disk_children && apr_hash_count(disk_children)) 458299425Smm { 459299425Smm apr_hash_t *marker_files; 460299425Smm 461299425Smm SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db, 462299425Smm src_abspath, scratch_pool, 463299425Smm scratch_pool)); 464299425Smm 465299425Smm work_items = NULL; 466299425Smm 467299425Smm for (hi = apr_hash_first(scratch_pool, disk_children); hi; 468299425Smm hi = apr_hash_next(hi)) 469299425Smm { 470299425Smm const char *name = svn__apr_hash_index_key(hi); 471299425Smm const char *unver_src_abspath, *unver_dst_abspath; 472299425Smm svn_skel_t *work_item; 473299425Smm 474299425Smm if (svn_wc_is_adm_dir(name, iterpool)) 475299425Smm continue; 476299425Smm 477299425Smm if (cancel_func) 478299425Smm SVN_ERR(cancel_func(cancel_baton)); 479299425Smm 480299425Smm svn_pool_clear(iterpool); 481299425Smm unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool); 482299425Smm unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool); 483299425Smm 484299425Smm if (marker_files && 485299425Smm svn_hash_gets(marker_files, unver_src_abspath)) 486299425Smm continue; 487299425Smm 488299425Smm SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath, 489299425Smm unver_dst_abspath, tmpdir_abspath, 490299425Smm TRUE /* recursive */, TRUE /* unversioned */, 491299425Smm cancel_func, cancel_baton, 492299425Smm scratch_pool, iterpool)); 493299425Smm 494299425Smm if (work_item) 495299425Smm work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); 496299425Smm } 497299425Smm SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool)); 498299425Smm } 499299425Smm 500299425Smm svn_pool_destroy(iterpool); 501299425Smm 502299425Smm return SVN_NO_ERROR; 503299425Smm} 504299425Smm 505299425Smm 506299425Smm/* The guts of svn_wc_copy3() and svn_wc_move(). 507299425Smm * The additional parameter IS_MOVE indicates whether this is a copy or 508299425Smm * a move operation. 509299425Smm * 510299425Smm * If MOVE_DEGRADED_TO_COPY is not NULL and a move had to be degraded 511299425Smm * to a copy, then set *MOVE_DEGRADED_TO_COPY. */ 512299425Smmstatic svn_error_t * 513299425Smmcopy_or_move(svn_boolean_t *move_degraded_to_copy, 514299425Smm svn_wc_context_t *wc_ctx, 515299425Smm const char *src_abspath, 516299425Smm const char *dst_abspath, 517299425Smm svn_boolean_t metadata_only, 518299425Smm svn_boolean_t is_move, 519311042Smm svn_boolean_t allow_mixed_revisions, 520299425Smm svn_cancel_func_t cancel_func, 521299425Smm void *cancel_baton, 522299425Smm svn_wc_notify_func2_t notify_func, 523299425Smm void *notify_baton, 524299425Smm apr_pool_t *scratch_pool) 525299425Smm{ 526299425Smm svn_wc__db_t *db = wc_ctx->db; 527299425Smm svn_node_kind_t src_db_kind; 528299425Smm const char *dstdir_abspath; 529299425Smm svn_boolean_t conflicted; 530299425Smm const char *tmpdir_abspath; 531299425Smm const char *src_wcroot_abspath; 532299425Smm const char *dst_wcroot_abspath; 533299425Smm svn_boolean_t within_one_wc; 534299425Smm svn_wc__db_status_t src_status; 535299425Smm svn_error_t *err; 536299425Smm 537299425Smm SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); 538299425Smm SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 539299425Smm 540299425Smm dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); 541299425Smm 542299425Smm /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH; 543299425Smm throw an error if not. */ 544299425Smm { 545299425Smm svn_wc__db_status_t dstdir_status; 546299425Smm const char *src_repos_root_url, *dst_repos_root_url; 547299425Smm const char *src_repos_uuid, *dst_repos_uuid; 548299425Smm const char *src_repos_relpath; 549299425Smm 550299425Smm err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL, 551299425Smm &src_repos_relpath, &src_repos_root_url, 552299425Smm &src_repos_uuid, NULL, NULL, NULL, NULL, NULL, 553299425Smm NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 554299425Smm NULL, &conflicted, NULL, NULL, NULL, NULL, 555299425Smm NULL, NULL, 556299425Smm db, src_abspath, scratch_pool, scratch_pool); 557299425Smm 558299425Smm if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 559299425Smm { 560299425Smm /* Replicate old error code and text */ 561299425Smm svn_error_clear(err); 562299425Smm return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, 563299425Smm _("'%s' is not under version control"), 564299425Smm svn_dirent_local_style(src_abspath, 565299425Smm scratch_pool)); 566299425Smm } 567299425Smm else 568299425Smm SVN_ERR(err); 569299425Smm 570299425Smm /* Do this now, as we know the right data is cached */ 571299425Smm SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath, 572299425Smm scratch_pool, scratch_pool)); 573299425Smm 574299425Smm switch (src_status) 575299425Smm { 576299425Smm case svn_wc__db_status_deleted: 577299425Smm return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 578299425Smm _("Deleted node '%s' can't be copied."), 579299425Smm svn_dirent_local_style(src_abspath, 580299425Smm scratch_pool)); 581299425Smm 582299425Smm case svn_wc__db_status_excluded: 583299425Smm case svn_wc__db_status_server_excluded: 584299425Smm case svn_wc__db_status_not_present: 585299425Smm return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 586299425Smm _("The node '%s' was not found."), 587299425Smm svn_dirent_local_style(src_abspath, 588299425Smm scratch_pool)); 589299425Smm default: 590299425Smm break; 591299425Smm } 592299425Smm 593299425Smm if (is_move && ! strcmp(src_abspath, src_wcroot_abspath)) 594299425Smm { 595299425Smm return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 596299425Smm _("'%s' is the root of a working copy and " 597299425Smm "cannot be moved"), 598299425Smm svn_dirent_local_style(src_abspath, 599299425Smm scratch_pool)); 600299425Smm } 601299425Smm if (is_move && src_repos_relpath && !src_repos_relpath[0]) 602299425Smm { 603299425Smm return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 604311042Smm _("'%s' represents the repository root " 605299425Smm "and cannot be moved"), 606299425Smm svn_dirent_local_style(src_abspath, 607299425Smm scratch_pool)); 608299425Smm } 609299425Smm 610299425Smm err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL, 611299425Smm &dst_repos_root_url, &dst_repos_uuid, NULL, 612299425Smm NULL, NULL, NULL, NULL, NULL, NULL, NULL, 613299425Smm NULL, NULL, NULL, NULL, NULL, NULL, 614299425Smm NULL, NULL, NULL, NULL, 615299425Smm NULL, NULL, NULL, 616299425Smm db, dstdir_abspath, 617299425Smm scratch_pool, scratch_pool); 618299425Smm 619299425Smm if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 620299425Smm { 621299425Smm /* An unversioned destination directory exists on disk. */ 622299425Smm svn_error_clear(err); 623299425Smm return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, 624299425Smm _("'%s' is not under version control"), 625299425Smm svn_dirent_local_style(dstdir_abspath, 626299425Smm scratch_pool)); 627299425Smm } 628299425Smm else 629299425Smm SVN_ERR(err); 630299425Smm 631299425Smm /* Do this now, as we know the right data is cached */ 632299425Smm SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath, 633299425Smm scratch_pool, scratch_pool)); 634299425Smm 635299425Smm if (!src_repos_root_url) 636299425Smm { 637299425Smm if (src_status == svn_wc__db_status_added) 638299425Smm SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, 639299425Smm &src_repos_root_url, 640299425Smm &src_repos_uuid, NULL, NULL, NULL, 641299425Smm NULL, 642299425Smm db, src_abspath, 643299425Smm scratch_pool, scratch_pool)); 644299425Smm else 645299425Smm /* If not added, the node must have a base or we can't copy */ 646299425Smm SVN_ERR(svn_wc__db_scan_base_repos(NULL, &src_repos_root_url, 647299425Smm &src_repos_uuid, 648299425Smm db, src_abspath, 649299425Smm scratch_pool, scratch_pool)); 650299425Smm } 651299425Smm 652299425Smm if (!dst_repos_root_url) 653299425Smm { 654299425Smm if (dstdir_status == svn_wc__db_status_added) 655299425Smm SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, 656299425Smm &dst_repos_root_url, 657299425Smm &dst_repos_uuid, NULL, NULL, NULL, 658299425Smm NULL, 659299425Smm db, dstdir_abspath, 660299425Smm scratch_pool, scratch_pool)); 661299425Smm else 662299425Smm /* If not added, the node must have a base or we can't copy */ 663299425Smm SVN_ERR(svn_wc__db_scan_base_repos(NULL, &dst_repos_root_url, 664299425Smm &dst_repos_uuid, 665299425Smm db, dstdir_abspath, 666299425Smm scratch_pool, scratch_pool)); 667299425Smm } 668299425Smm 669299425Smm if (strcmp(src_repos_root_url, dst_repos_root_url) != 0 670299425Smm || strcmp(src_repos_uuid, dst_repos_uuid) != 0) 671299425Smm return svn_error_createf( 672299425Smm SVN_ERR_WC_INVALID_SCHEDULE, NULL, 673299425Smm _("Cannot copy to '%s', as it is not from repository '%s'; " 674299425Smm "it is from '%s'"), 675299425Smm svn_dirent_local_style(dst_abspath, scratch_pool), 676299425Smm src_repos_root_url, dst_repos_root_url); 677299425Smm 678299425Smm if (dstdir_status == svn_wc__db_status_deleted) 679299425Smm return svn_error_createf( 680299425Smm SVN_ERR_WC_INVALID_SCHEDULE, NULL, 681299425Smm _("Cannot copy to '%s' as it is scheduled for deletion"), 682299425Smm svn_dirent_local_style(dst_abspath, scratch_pool)); 683299425Smm /* ### should report dstdir_abspath instead of dst_abspath? */ 684299425Smm } 685299425Smm 686299425Smm /* TODO(#2843): Rework the error report. */ 687299425Smm /* Check if the copy target is missing or hidden and thus not exist on the 688299425Smm disk, before actually doing the file copy. */ 689299425Smm { 690299425Smm svn_wc__db_status_t dst_status; 691299425Smm 692299425Smm err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, 693299425Smm NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 694299425Smm NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 695299425Smm NULL, NULL, NULL, NULL, NULL, 696299425Smm db, dst_abspath, scratch_pool, scratch_pool); 697299425Smm 698299425Smm if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 699299425Smm return svn_error_trace(err); 700299425Smm 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_abspath; 895 896 marker_abspath = APR_ARRAY_IDX(markers, i, const char *); 897 898 child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath); 899 900 if (child_relpath) 901 { 902 child_abspath = svn_dirent_join(dst_dir, child_relpath, 903 scratch_pool); 904 905 SVN_ERR(svn_io_remove_file2(child_abspath, 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 *dst_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(dst_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(dst_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 { 1037 /* When we moved a directory, we moved the conflict markers 1038 with the target... if we moved a file we only moved the 1039 file itself and the markers are still in the old location */ 1040 SVN_ERR(remove_node_conflict_markers(db, src_abspath, 1041 (kind == svn_node_dir) 1042 ? dst_abspath 1043 : src_abspath, 1044 scratch_pool)); 1045 } 1046 1047 SVN_ERR(svn_wc__db_op_delete(db, src_abspath, 1048 move_degraded_to_copy ? NULL : dst_abspath, 1049 TRUE /* delete_dir_externals */, 1050 NULL /* conflict */, NULL /* work_items */, 1051 cancel_func, cancel_baton, 1052 notify_func, notify_baton, 1053 scratch_pool)); 1054 1055 return SVN_NO_ERROR; 1056} 1057