2 * copy.c: wc 'copy' functionality.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_dirent_uri.h"
38 #include "workqueue.h"
40 #include "conflicts.h"
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
45 /* #define RECORD_MIXED_MOVE */
49 /* Make a copy of the filesystem node (or tree if RECURSIVE) at
50 SRC_ABSPATH under a temporary name in the directory
51 TMPDIR_ABSPATH and return the absolute path of the copy in
52 *DST_ABSPATH. Return the node kind of SRC_ABSPATH in *KIND. If
53 SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate
54 that no copy was made.
56 If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
57 RECORDED_SIZE (if not SVN_INVALID_FILESIZE) contains the recorded size of
58 SRC_ABSPATH, and RECORDED_TIME the recorded size or 0.
60 These values will be used to avoid unneeded work.
63 copy_to_tmpdir(svn_skel_t **work_item,
64 svn_node_kind_t *kind,
66 const char *src_abspath,
67 const char *dst_abspath,
68 const char *tmpdir_abspath,
69 svn_boolean_t file_copy,
70 svn_boolean_t unversioned,
71 const svn_io_dirent2_t *dirent,
72 svn_filesize_t recorded_size,
73 apr_time_t recorded_time,
74 svn_cancel_func_t cancel_func,
76 apr_pool_t *result_pool,
77 apr_pool_t *scratch_pool)
79 svn_boolean_t is_special;
80 svn_io_file_del_t delete_when;
81 const char *dst_tmp_abspath;
82 svn_node_kind_t dsk_kind;
91 is_special = dirent->special;
94 SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
96 if (*kind == svn_node_none)
100 else if (*kind == svn_node_unknown)
102 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
103 _("Source '%s' is unexpected kind"),
104 svn_dirent_local_style(src_abspath,
107 else if (*kind == svn_node_dir || is_special)
108 delete_when = svn_io_file_del_on_close;
109 else /* the default case: (*kind == svn_node_file) */
110 delete_when = svn_io_file_del_none;
112 /* ### Do we need a pool cleanup to remove the copy? We can't use
113 ### svn_io_file_del_on_pool_cleanup above because a) it won't
114 ### handle the directory case and b) we need to be able to remove
115 ### the cleanup before queueing the move work item. */
117 if (file_copy && !unversioned)
119 svn_boolean_t modified;
120 /* It's faster to look for mods on the source now, as
121 the timestamp might match, than to examine the
122 destination later as the destination timestamp will
126 && dirent->kind == svn_node_file
127 && recorded_size != SVN_INVALID_FILESIZE
128 && recorded_size == dirent->filesize
129 && recorded_time == dirent->mtime)
131 modified = FALSE; /* Recorded matches on-disk. Easy out */
135 SVN_ERR(svn_wc__internal_file_modified_p(&modified, db, src_abspath,
136 FALSE, scratch_pool));
141 /* Why create a temp copy if we can just reinstall from pristine? */
142 SVN_ERR(svn_wc__wq_build_file_install(work_item,
143 db, dst_abspath, NULL, FALSE,
145 result_pool, scratch_pool));
149 else if (*kind == svn_node_dir && !file_copy)
151 /* Just build a new direcory from the workqueue */
152 SVN_ERR(svn_wc__wq_build_dir_install(work_item,
154 result_pool, scratch_pool));
159 /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave
160 a file there and then overwrite it; otherwise leave no node on disk at
161 that path. In the latter case, something else might use that path
162 before we get around to using it a moment later, but never mind. */
163 SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath,
164 delete_when, scratch_pool, scratch_pool));
166 if (*kind == svn_node_dir)
169 SVN_ERR(svn_io_copy_dir_recursively(
172 svn_dirent_basename(dst_tmp_abspath, scratch_pool),
173 TRUE, /* copy_perms */
174 cancel_func, cancel_baton,
177 SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool));
179 else if (!is_special)
180 SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath,
181 TRUE /* copy_perms */,
184 SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool));
188 /* Remove 'read-only' from the destination file; it's a local add now. */
189 SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath,
190 FALSE, scratch_pool));
193 SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath,
194 dst_tmp_abspath, dst_abspath,
195 result_pool, scratch_pool));
200 /* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB.
201 If METADATA_ONLY is true, copy only the versioned metadata,
202 otherwise copy both the versioned metadata and the filesystem node (even
203 if it is the wrong kind, and recursively if it is a dir).
205 If IS_MOVE is true, record move information in working copy meta
206 data in addition to copying the file.
208 If the versioned file has a text conflict, and the .mine file exists in
209 the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the
210 versioned file itself.
212 This also works for versioned symlinks that are stored in the db as
213 svn_node_file with svn:special set.
215 If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
216 RECORDED_SIZE (if not SVN_INVALID_FILESIZE) contains the recorded size of
217 SRC_ABSPATH, and RECORDED_TIME the recorded size or 0.
219 These values will be used to avoid unneeded work.
222 copy_versioned_file(svn_wc__db_t *db,
223 const char *src_abspath,
224 const char *dst_abspath,
225 const char *dst_op_root_abspath,
226 const char *tmpdir_abspath,
227 svn_boolean_t metadata_only,
228 svn_boolean_t conflicted,
229 svn_boolean_t is_move,
230 const svn_io_dirent2_t *dirent,
231 svn_filesize_t recorded_size,
232 apr_time_t recorded_time,
233 svn_cancel_func_t cancel_func,
235 svn_wc_notify_func2_t notify_func,
237 apr_pool_t *scratch_pool)
239 svn_skel_t *work_items = NULL;
241 /* In case we are copying from one WC to another (e.g. an external dir),
242 ensure the destination WC has a copy of the pristine text. */
244 /* Prepare a temp copy of the filesystem node. It is usually a file, but
245 copy recursively if it's a dir. */
248 const char *my_src_abspath = NULL;
249 svn_boolean_t handle_as_unversioned = FALSE;
251 /* By default, take the copy source as given. */
252 my_src_abspath = src_abspath;
256 svn_skel_t *conflict;
257 const char *conflict_working;
260 /* Is there a text conflict at the source path? */
261 SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
263 scratch_pool, scratch_pool));
265 err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
266 db, src_abspath, conflict,
270 if (err && err->apr_err == SVN_ERR_WC_MISSING)
272 /* not text conflicted */
273 svn_error_clear(err);
274 conflict_working = NULL;
279 if (conflict_working)
281 svn_node_kind_t working_kind;
283 /* Does the ".mine" file exist? */
284 SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
287 if (working_kind == svn_node_file)
289 /* Don't perform unmodified/pristine optimization */
290 handle_as_unversioned = TRUE;
291 my_src_abspath = conflict_working;
296 SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath,
297 dst_abspath, tmpdir_abspath,
298 TRUE /* file_copy */,
299 handle_as_unversioned /* unversioned */,
300 dirent, recorded_size, recorded_time,
301 cancel_func, cancel_baton,
302 scratch_pool, scratch_pool));
305 /* Copy the (single) node's metadata, and move the new filesystem node
307 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
308 dst_op_root_abspath, is_move, work_items,
313 svn_wc_notify_t *notify
314 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
316 notify->kind = svn_node_file;
318 (*notify_func)(notify_baton, notify, scratch_pool);
323 /* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB,
324 recursively. If METADATA_ONLY is true, copy only the versioned metadata,
325 otherwise copy both the versioned metadata and the filesystem nodes (even
326 if they are the wrong kind, and including unversioned children).
327 If IS_MOVE is true, record move information in working copy meta
328 data in addition to copying the directory.
330 WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
332 If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
335 copy_versioned_dir(svn_wc__db_t *db,
336 const char *src_abspath,
337 const char *dst_abspath,
338 const char *dst_op_root_abspath,
339 const char *tmpdir_abspath,
340 svn_boolean_t metadata_only,
341 svn_boolean_t is_move,
342 const svn_io_dirent2_t *dirent,
343 svn_cancel_func_t cancel_func,
345 svn_wc_notify_func2_t notify_func,
347 apr_pool_t *scratch_pool)
349 svn_skel_t *work_items = NULL;
350 const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
351 apr_hash_t *versioned_children;
352 apr_hash_t *conflicted_children;
353 apr_hash_t *disk_children;
354 apr_hash_index_t *hi;
355 svn_node_kind_t disk_kind;
356 apr_pool_t *iterpool;
358 /* Prepare a temp copy of the single filesystem node (usually a dir). */
361 SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind,
362 db, src_abspath, dst_abspath,
364 FALSE /* file_copy */,
365 FALSE /* unversioned */,
366 dirent, SVN_INVALID_FILESIZE, 0,
367 cancel_func, cancel_baton,
368 scratch_pool, scratch_pool));
371 /* Copy the (single) node's metadata, and move the new filesystem node
373 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
374 dst_op_root_abspath, is_move, work_items,
379 svn_wc_notify_t *notify
380 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
382 notify->kind = svn_node_dir;
384 /* When we notify that we performed a copy, make sure we already did */
385 if (work_items != NULL)
386 SVN_ERR(svn_wc__wq_run(db, dir_abspath,
387 cancel_func, cancel_baton, scratch_pool));
389 (*notify_func)(notify_baton, notify, scratch_pool);
392 if (!metadata_only && disk_kind == svn_node_dir)
393 /* All filesystem children, versioned and unversioned. We're only
394 interested in their names, so we can pass TRUE as the only_check_type
396 SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
397 scratch_pool, scratch_pool));
399 disk_children = NULL;
401 /* Copy all the versioned children */
402 iterpool = svn_pool_create(scratch_pool);
403 SVN_ERR(svn_wc__db_read_children_info(&versioned_children,
404 &conflicted_children,
406 FALSE /* base_tree_only */,
407 scratch_pool, iterpool));
408 for (hi = apr_hash_first(scratch_pool, versioned_children);
410 hi = apr_hash_next(hi))
412 const char *child_name, *child_src_abspath, *child_dst_abspath;
413 struct svn_wc__db_info_t *info;
415 svn_pool_clear(iterpool);
418 SVN_ERR(cancel_func(cancel_baton));
420 child_name = apr_hash_this_key(hi);
421 info = apr_hash_this_val(hi);
422 child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool);
423 child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool);
426 SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
432 if (info->status == svn_wc__db_status_normal
433 || info->status == svn_wc__db_status_added)
435 /* We have more work to do than just changing the DB */
436 if (info->kind == svn_node_file)
438 /* We should skip this node if this child is a file external
439 (issues #3589, #4000) */
440 if (!info->file_external)
441 SVN_ERR(copy_versioned_file(db,
446 metadata_only, info->conflicted,
449 ? svn_hash_gets(disk_children,
454 cancel_func, cancel_baton,
458 else if (info->kind == svn_node_dir)
459 SVN_ERR(copy_versioned_dir(db,
460 child_src_abspath, child_dst_abspath,
461 dst_op_root_abspath, tmpdir_abspath,
462 metadata_only, is_move,
464 ? svn_hash_gets(disk_children,
467 cancel_func, cancel_baton, NULL, NULL,
470 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
471 _("cannot handle node kind for '%s'"),
472 svn_dirent_local_style(child_src_abspath,
475 else if (info->status == svn_wc__db_status_deleted
476 || info->status == svn_wc__db_status_not_present
477 || info->status == svn_wc__db_status_excluded)
479 /* This will be copied as some kind of deletion. Don't touch
481 SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath,
482 child_dst_abspath, dst_op_root_abspath,
483 is_move, NULL, iterpool));
485 /* Don't recurse on children when all we do is creating not-present
488 else if (info->status == svn_wc__db_status_incomplete)
490 /* Should go ahead and copy incomplete to incomplete? Try to
491 copy as much as possible, or give up early? */
492 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
493 _("Cannot handle status of '%s'"),
494 svn_dirent_local_style(child_src_abspath,
499 SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded);
501 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
502 _("Cannot copy '%s' excluded by server"),
503 svn_dirent_local_style(child_src_abspath,
508 && (info->status == svn_wc__db_status_normal
509 || info->status == svn_wc__db_status_added))
511 /* Remove versioned child as it has been handled */
512 svn_hash_sets(disk_children, child_name, NULL);
516 /* Copy the remaining filesystem children, which are unversioned, skipping
517 any conflict-marker files. */
518 if (disk_children && apr_hash_count(disk_children))
520 apr_hash_t *marker_files;
522 SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
523 src_abspath, scratch_pool,
528 for (hi = apr_hash_first(scratch_pool, disk_children); hi;
529 hi = apr_hash_next(hi))
531 const char *name = apr_hash_this_key(hi);
532 const char *unver_src_abspath, *unver_dst_abspath;
533 svn_skel_t *work_item;
535 if (svn_wc_is_adm_dir(name, iterpool))
539 SVN_ERR(cancel_func(cancel_baton));
541 svn_pool_clear(iterpool);
542 unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool);
543 unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool);
546 svn_hash_gets(marker_files, unver_src_abspath))
549 SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath,
550 unver_dst_abspath, tmpdir_abspath,
551 TRUE /* recursive */, TRUE /* unversioned */,
552 NULL, SVN_INVALID_FILESIZE, 0,
553 cancel_func, cancel_baton,
554 scratch_pool, iterpool));
557 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
559 SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool));
562 svn_pool_destroy(iterpool);
568 /* The guts of svn_wc_copy3() and svn_wc_move().
569 * The additional parameter IS_MOVE indicates whether this is a copy or
572 * If RECORD_MOVE_ON_DELETE is not NULL and a move had to be degraded
573 * to a copy, then set *RECORD_MOVE_ON_DELETE to FALSE. */
575 copy_or_move(svn_boolean_t *record_move_on_delete,
576 svn_wc_context_t *wc_ctx,
577 const char *src_abspath,
578 const char *dst_abspath,
579 svn_boolean_t metadata_only,
580 svn_boolean_t is_move,
581 svn_boolean_t allow_mixed_revisions,
582 svn_cancel_func_t cancel_func,
584 svn_wc_notify_func2_t notify_func,
586 apr_pool_t *scratch_pool)
588 svn_wc__db_t *db = wc_ctx->db;
589 svn_node_kind_t src_db_kind;
590 const char *dstdir_abspath;
591 svn_boolean_t conflicted;
592 const char *tmpdir_abspath;
593 const char *src_wcroot_abspath;
594 const char *dst_wcroot_abspath;
595 svn_boolean_t within_one_wc;
596 svn_wc__db_status_t src_status;
598 svn_filesize_t recorded_size;
599 apr_time_t recorded_time;
601 SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
602 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
604 dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
606 /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
607 throw an error if not. */
609 svn_wc__db_status_t dstdir_status;
610 const char *src_repos_root_url, *dst_repos_root_url;
611 const char *src_repos_uuid, *dst_repos_uuid;
612 const char *src_repos_relpath;
614 err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL,
615 &src_repos_relpath, &src_repos_root_url,
616 &src_repos_uuid, NULL, NULL, NULL, NULL, NULL,
617 NULL, NULL, NULL, NULL, NULL, NULL,
618 &recorded_size, &recorded_time,
619 NULL, &conflicted, NULL, NULL, NULL, NULL,
621 db, src_abspath, scratch_pool, scratch_pool);
623 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
625 /* Replicate old error code and text */
626 svn_error_clear(err);
627 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
628 _("'%s' is not under version control"),
629 svn_dirent_local_style(src_abspath,
635 /* Do this now, as we know the right data is cached */
636 SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath,
637 scratch_pool, scratch_pool));
641 case svn_wc__db_status_deleted:
642 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
643 _("Deleted node '%s' can't be copied."),
644 svn_dirent_local_style(src_abspath,
647 case svn_wc__db_status_excluded:
648 case svn_wc__db_status_server_excluded:
649 case svn_wc__db_status_not_present:
650 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
651 _("The node '%s' was not found."),
652 svn_dirent_local_style(src_abspath,
658 if (is_move && ! strcmp(src_abspath, src_wcroot_abspath))
660 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
661 _("'%s' is the root of a working copy and "
663 svn_dirent_local_style(src_abspath,
666 if (is_move && src_repos_relpath && !src_repos_relpath[0])
668 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
669 _("'%s' represents the repository root "
670 "and cannot be moved"),
671 svn_dirent_local_style(src_abspath,
675 err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL,
676 &dst_repos_root_url, &dst_repos_uuid, NULL,
677 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
678 NULL, NULL, NULL, NULL, NULL, NULL,
679 NULL, NULL, NULL, NULL,
682 scratch_pool, scratch_pool);
684 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
686 /* An unversioned destination directory exists on disk. */
687 svn_error_clear(err);
688 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
689 _("'%s' is not under version control"),
690 svn_dirent_local_style(dstdir_abspath,
696 /* Do this now, as we know the right data is cached */
697 SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath,
698 scratch_pool, scratch_pool));
700 if (!src_repos_root_url)
702 if (src_status == svn_wc__db_status_added)
703 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
705 &src_repos_uuid, NULL, NULL, NULL,
708 scratch_pool, scratch_pool));
710 /* If not added, the node must have a base or we can't copy */
711 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL,
713 &src_repos_uuid, NULL, NULL, NULL,
714 NULL, NULL, NULL, NULL, NULL, NULL,
717 scratch_pool, scratch_pool));
720 if (!dst_repos_root_url)
722 if (dstdir_status == svn_wc__db_status_added)
723 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
725 &dst_repos_uuid, NULL, NULL, NULL,
728 scratch_pool, scratch_pool));
730 /* If not added, the node must have a base or we can't copy */
731 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL,
733 &dst_repos_uuid, NULL, NULL, NULL,
734 NULL, NULL, NULL, NULL, NULL, NULL,
737 scratch_pool, scratch_pool));
740 if (strcmp(src_repos_root_url, dst_repos_root_url) != 0
741 || strcmp(src_repos_uuid, dst_repos_uuid) != 0)
742 return svn_error_createf(
743 SVN_ERR_WC_INVALID_SCHEDULE, NULL,
744 _("Cannot copy to '%s', as it is not from repository '%s'; "
746 svn_dirent_local_style(dst_abspath, scratch_pool),
747 src_repos_root_url, dst_repos_root_url);
749 if (dstdir_status == svn_wc__db_status_deleted)
750 return svn_error_createf(
751 SVN_ERR_WC_INVALID_SCHEDULE, NULL,
752 _("Cannot copy to '%s' as it is scheduled for deletion"),
753 svn_dirent_local_style(dst_abspath, scratch_pool));
754 /* ### should report dstdir_abspath instead of dst_abspath? */
757 /* TODO(#2843): Rework the error report. */
758 /* Check if the copy target is missing or hidden and thus not exist on the
759 disk, before actually doing the file copy. */
761 svn_wc__db_status_t dst_status;
763 err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL,
764 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
765 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
766 NULL, NULL, NULL, NULL, NULL,
767 db, dst_abspath, scratch_pool, scratch_pool);
769 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
770 return svn_error_trace(err);
772 svn_error_clear(err);
777 case svn_wc__db_status_excluded:
778 return svn_error_createf(
779 SVN_ERR_ENTRY_EXISTS, NULL,
780 _("'%s' is already under version control "
782 svn_dirent_local_style(dst_abspath, scratch_pool));
783 case svn_wc__db_status_server_excluded:
784 return svn_error_createf(
785 SVN_ERR_ENTRY_EXISTS, NULL,
786 _("'%s' is already under version control"),
787 svn_dirent_local_style(dst_abspath, scratch_pool));
789 case svn_wc__db_status_deleted:
790 case svn_wc__db_status_not_present:
791 break; /* OK to add */
794 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
795 _("There is already a versioned item '%s'"),
796 svn_dirent_local_style(dst_abspath,
801 /* Check that the target path is not obstructed, if required. */
804 svn_node_kind_t dst_kind;
806 /* (We need only to check the root of the copy, not every path inside
807 copy_versioned_file/_dir.) */
808 SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
809 if (dst_kind != svn_node_none)
810 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
811 _("'%s' already exists and is in the way"),
812 svn_dirent_local_style(dst_abspath,
816 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
818 scratch_pool, scratch_pool));
820 within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
825 if (record_move_on_delete)
826 *record_move_on_delete = FALSE;
832 SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
833 cancel_func, cancel_baton,
836 if (src_db_kind == svn_node_file
837 || src_db_kind == svn_node_symlink)
839 err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
841 metadata_only, conflicted, is_move,
842 NULL, recorded_size, recorded_time,
843 cancel_func, cancel_baton,
844 notify_func, notify_baton,
850 && src_status == svn_wc__db_status_normal)
852 svn_revnum_t min_rev;
853 svn_revnum_t max_rev;
855 /* Verify that the move source is a single-revision subtree. */
856 SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
857 src_abspath, FALSE, scratch_pool));
858 if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
861 if (!allow_mixed_revisions)
862 return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
863 _("Cannot move mixed-revision "
864 "subtree '%s' [%ld:%ld]; "
865 "try updating it first"),
866 svn_dirent_local_style(src_abspath,
870 #ifndef RECORD_MIXED_MOVE
872 if (record_move_on_delete)
873 *record_move_on_delete = FALSE;
878 err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
879 tmpdir_abspath, metadata_only, is_move,
881 cancel_func, cancel_baton,
882 notify_func, notify_baton,
886 if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
887 return svn_error_trace(err);
890 err = svn_error_compose_create(err,
891 svn_wc__db_op_handle_move_back(NULL,
892 db, dst_abspath, src_abspath,
893 NULL /* work_items */,
896 /* Run the work queue with the remaining work */
897 SVN_ERR(svn_error_compose_create(
899 svn_wc__wq_run(db, dst_abspath,
900 cancel_func, cancel_baton,
907 /* Public Interface */
910 svn_wc_copy3(svn_wc_context_t *wc_ctx,
911 const char *src_abspath,
912 const char *dst_abspath,
913 svn_boolean_t metadata_only,
914 svn_cancel_func_t cancel_func,
916 svn_wc_notify_func2_t notify_func,
918 apr_pool_t *scratch_pool)
920 /* Verify that we have the required write lock. */
921 SVN_ERR(svn_wc__write_check(wc_ctx->db,
922 svn_dirent_dirname(dst_abspath, scratch_pool),
925 return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath,
926 metadata_only, FALSE /* is_move */,
927 TRUE /* allow_mixed_revisions */,
928 cancel_func, cancel_baton,
929 notify_func, notify_baton,
934 /* Remove the conflict markers of NODE_ABSPATH, that were left over after
935 copying NODE_ABSPATH from SRC_ABSPATH.
937 Only use this function when you know what you're doing. This function
938 explicitly ignores some case insensitivity issues!
942 remove_node_conflict_markers(svn_wc__db_t *db,
943 const char *src_abspath,
944 const char *node_abspath,
945 apr_pool_t *scratch_pool)
947 svn_skel_t *conflict;
949 SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
951 scratch_pool, scratch_pool));
953 /* Do we have conflict markers that should be removed? */
954 if (conflict != NULL)
956 const apr_array_header_t *markers;
958 const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
959 const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
961 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
963 scratch_pool, scratch_pool));
965 /* No iterpool: Maximum number of possible conflict markers is 4 */
966 for (i = 0; markers && (i < markers->nelts); i++)
968 const char *marker_abspath;
969 const char *child_relpath;
970 const char *child_abspath;
972 marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
974 child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath);
978 child_abspath = svn_dirent_join(dst_dir, child_relpath,
981 SVN_ERR(svn_io_remove_file2(child_abspath, TRUE, scratch_pool));
989 /* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
990 after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
992 This function doesn't remove the conflict markers on WC_DIR_ABSPATH
995 Only use this function when you know what you're doing. This function
996 explicitly ignores some case insensitivity issues!
999 remove_all_conflict_markers(svn_wc__db_t *db,
1000 const char *src_dir_abspath,
1001 const char *dst_dir_abspath,
1002 svn_cancel_func_t cancel_func,
1004 apr_pool_t *scratch_pool)
1006 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1008 apr_hash_t *conflicts; /* Unused */
1009 apr_hash_index_t *hi;
1011 /* Reuse a status helper to obtain all subdirs and conflicts in a single
1013 /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
1015 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
1017 FALSE /* base_tree_only */,
1018 scratch_pool, iterpool));
1020 for (hi = apr_hash_first(scratch_pool, nodes);
1022 hi = apr_hash_next(hi))
1024 const char *name = apr_hash_this_key(hi);
1025 struct svn_wc__db_info_t *info = apr_hash_this_val(hi);
1028 SVN_ERR(cancel_func(cancel_baton));
1030 if (info->conflicted)
1032 svn_pool_clear(iterpool);
1033 SVN_ERR(remove_node_conflict_markers(
1035 svn_dirent_join(src_dir_abspath, name, iterpool),
1036 svn_dirent_join(dst_dir_abspath, name, iterpool),
1039 if (info->kind == svn_node_dir)
1041 svn_pool_clear(iterpool);
1042 SVN_ERR(remove_all_conflict_markers(
1044 svn_dirent_join(src_dir_abspath, name, iterpool),
1045 svn_dirent_join(dst_dir_abspath, name, iterpool),
1046 cancel_func, cancel_baton,
1051 svn_pool_destroy(iterpool);
1052 return SVN_NO_ERROR;
1056 svn_wc__move2(svn_wc_context_t *wc_ctx,
1057 const char *src_abspath,
1058 const char *dst_abspath,
1059 svn_boolean_t metadata_only,
1060 svn_boolean_t allow_mixed_revisions,
1061 svn_cancel_func_t cancel_func,
1063 svn_wc_notify_func2_t notify_func,
1065 apr_pool_t *scratch_pool)
1067 svn_wc__db_t *db = wc_ctx->db;
1068 svn_boolean_t record_on_delete = TRUE;
1069 svn_node_kind_t kind;
1070 svn_boolean_t conflicted;
1072 /* Verify that we have the required write locks. */
1073 SVN_ERR(svn_wc__write_check(wc_ctx->db,
1074 svn_dirent_dirname(src_abspath, scratch_pool),
1076 SVN_ERR(svn_wc__write_check(wc_ctx->db,
1077 svn_dirent_dirname(dst_abspath, scratch_pool),
1080 SVN_ERR(copy_or_move(&record_on_delete,
1081 wc_ctx, src_abspath, dst_abspath,
1082 TRUE /* metadata_only */,
1084 allow_mixed_revisions,
1085 cancel_func, cancel_baton,
1086 notify_func, notify_baton,
1089 /* An interrupt at this point will leave the new copy marked as
1090 moved-here but the source has not yet been deleted or marked as
1093 /* Should we be using a workqueue for this move? It's not clear.
1094 What should happen if the copy above is interrupted? The user
1095 may want to abort the move and a workqueue might interfere with
1098 BH: On Windows it is not unlikely to encounter an access denied on
1099 this line. Installing the move in the workqueue via the copy_or_move
1100 might make it hard to recover from that situation, while the DB
1101 is still in a valid state. So be careful when switching this over
1102 to the workqueue. */
1107 err = svn_error_trace(svn_io_file_rename(src_abspath, dst_abspath,
1110 /* Let's try if we can keep wc.db consistent even when the move
1111 fails. Deleting the target is a wc.db only operation, while
1112 going forward (delaying the error) would try to change
1113 conflict markers, which might also fail. */
1115 return svn_error_trace(
1116 svn_error_compose_create(
1118 svn_wc__db_op_delete(wc_ctx->db, dst_abspath, NULL, TRUE,
1119 NULL, NULL, cancel_func, cancel_baton,
1124 SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
1125 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1126 NULL, NULL, NULL, NULL, NULL, NULL,
1127 &conflicted, NULL, NULL, NULL,
1130 scratch_pool, scratch_pool));
1132 if (kind == svn_node_dir)
1133 SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
1134 cancel_func, cancel_baton,
1139 /* When we moved a directory, we moved the conflict markers
1140 with the target... if we moved a file we only moved the
1141 file itself and the markers are still in the old location */
1142 SVN_ERR(remove_node_conflict_markers(db, src_abspath,
1143 (kind == svn_node_dir)
1149 SVN_ERR(svn_wc__db_op_delete(db, src_abspath,
1150 record_on_delete ? dst_abspath : NULL,
1151 TRUE /* delete_dir_externals */,
1152 NULL /* conflict */, NULL /* work_items */,
1153 cancel_func, cancel_baton,
1154 notify_func, notify_baton,
1157 return SVN_NO_ERROR;