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"
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. */
55 copy_to_tmpdir(svn_skel_t **work_item,
56 svn_node_kind_t *kind,
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,
65 apr_pool_t *result_pool,
66 apr_pool_t *scratch_pool)
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;
77 SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
79 if (*kind == svn_node_none)
83 else if (*kind == svn_node_unknown)
85 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
86 _("Source '%s' is unexpected kind"),
87 svn_dirent_local_style(src_abspath,
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;
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. */
100 if (file_copy && !unversioned)
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
107 SVN_ERR(svn_wc__internal_file_modified_p(&modified,
109 FALSE, scratch_pool));
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,
116 result_pool, scratch_pool));
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));
128 if (*kind == svn_node_dir)
131 SVN_ERR(svn_io_copy_dir_recursively(
134 svn_dirent_basename(dst_tmp_abspath, scratch_pool),
135 TRUE, /* copy_perms */
136 cancel_func, cancel_baton,
139 SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool));
141 else if (!is_special)
142 SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath,
143 TRUE /* copy_perms */,
146 SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool));
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));
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));
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).
167 If IS_MOVE is true, record move information in working copy meta
168 data in addition to copying the file.
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.
174 This also works for versioned symlinks that are stored in the db as
175 svn_node_file with svn:special set. */
177 copy_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,
187 svn_wc_notify_func2_t notify_func,
189 apr_pool_t *scratch_pool)
191 svn_skel_t *work_items = NULL;
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. */
196 /* Prepare a temp copy of the filesystem node. It is usually a file, but
197 copy recursively if it's a dir. */
200 const char *my_src_abspath = NULL;
201 svn_boolean_t handle_as_unversioned = FALSE;
203 /* By default, take the copy source as given. */
204 my_src_abspath = src_abspath;
208 svn_skel_t *conflict;
209 const char *conflict_working;
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));
216 err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
217 db, src_abspath, conflict,
221 if (err && err->apr_err == SVN_ERR_WC_MISSING)
223 /* not text conflicted */
224 svn_error_clear(err);
225 conflict_working = NULL;
230 if (conflict_working)
232 svn_node_kind_t working_kind;
234 /* Does the ".mine" file exist? */
235 SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
238 if (working_kind == svn_node_file)
240 /* Don't perform unmodified/pristine optimization */
241 handle_as_unversioned = TRUE;
242 my_src_abspath = conflict_working;
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));
255 /* Copy the (single) node's metadata, and move the new filesystem node
257 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
258 dst_op_root_abspath, is_move, work_items,
263 svn_wc_notify_t *notify
264 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
266 notify->kind = svn_node_file;
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);
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.
284 WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
287 copy_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,
296 svn_wc_notify_func2_t notify_func,
298 apr_pool_t *scratch_pool)
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;
309 /* Prepare a temp copy of the single filesystem node (usually a dir). */
312 SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind,
313 db, src_abspath, dst_abspath,
315 FALSE /* file_copy */,
316 FALSE /* unversioned */,
317 cancel_func, cancel_baton,
318 scratch_pool, scratch_pool));
321 /* Copy the (single) node's metadata, and move the new filesystem node
323 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
324 dst_op_root_abspath, is_move, work_items,
329 svn_wc_notify_t *notify
330 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
332 notify->kind = svn_node_dir;
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));
339 (*notify_func)(notify_baton, notify, scratch_pool);
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
346 SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
347 scratch_pool, scratch_pool));
349 disk_children = NULL;
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,
356 scratch_pool, iterpool));
357 for (hi = apr_hash_first(scratch_pool, versioned_children);
359 hi = apr_hash_next(hi))
361 const char *child_name, *child_src_abspath, *child_dst_abspath;
362 struct svn_wc__db_info_t *info;
364 svn_pool_clear(iterpool);
367 SVN_ERR(cancel_func(cancel_baton));
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);
375 SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
381 if (info->status == svn_wc__db_status_normal
382 || info->status == svn_wc__db_status_added)
384 /* We have more work to do than just changing the DB */
385 if (info->kind == svn_node_file)
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,
395 metadata_only, info->conflicted,
397 cancel_func, cancel_baton,
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,
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,
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)
418 /* This will be copied as some kind of deletion. Don't touch
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));
424 /* Don't recurse on children while all we do is creating not-present
427 else if (info->status == svn_wc__db_status_incomplete)
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,
438 SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded);
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,
447 && (info->status == svn_wc__db_status_normal
448 || info->status == svn_wc__db_status_added))
450 /* Remove versioned child as it has been handled */
451 svn_hash_sets(disk_children, child_name, NULL);
455 /* Copy the remaining filesystem children, which are unversioned, skipping
456 any conflict-marker files. */
457 if (disk_children && apr_hash_count(disk_children))
459 apr_hash_t *marker_files;
461 SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
462 src_abspath, scratch_pool,
467 for (hi = apr_hash_first(scratch_pool, disk_children); hi;
468 hi = apr_hash_next(hi))
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;
474 if (svn_wc_is_adm_dir(name, iterpool))
478 SVN_ERR(cancel_func(cancel_baton));
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);
485 svn_hash_gets(marker_files, unver_src_abspath))
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));
495 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
497 SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool));
500 svn_pool_destroy(iterpool);
506 /* The guts of svn_wc_copy3() and svn_wc_move().
507 * The additional parameter IS_MOVE indicates whether this is a copy or
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. */
513 copy_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,
522 svn_wc_notify_func2_t notify_func,
524 apr_pool_t *scratch_pool)
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;
537 SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
538 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
540 dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
542 /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
543 throw an error if not. */
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;
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,
556 db, src_abspath, scratch_pool, scratch_pool);
558 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
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,
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));
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,
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,
593 if (is_move && ! strcmp(src_abspath, src_wcroot_abspath))
595 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
596 _("'%s' is the root of a working copy and "
598 svn_dirent_local_style(src_abspath,
601 if (is_move && src_repos_relpath && !src_repos_relpath[0])
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,
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,
617 scratch_pool, scratch_pool);
619 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
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,
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));
635 if (!src_repos_root_url)
637 if (src_status == svn_wc__db_status_added)
638 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
640 &src_repos_uuid, NULL, NULL, NULL,
643 scratch_pool, scratch_pool));
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,
649 scratch_pool, scratch_pool));
652 if (!dst_repos_root_url)
654 if (dstdir_status == svn_wc__db_status_added)
655 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
657 &dst_repos_uuid, NULL, NULL, NULL,
660 scratch_pool, scratch_pool));
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,
666 scratch_pool, scratch_pool));
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'; "
675 svn_dirent_local_style(dst_abspath, scratch_pool),
676 src_repos_root_url, dst_repos_root_url);
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? */
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. */
690 svn_wc__db_status_t dst_status;
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);
698 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
699 return svn_error_trace(err);
701 svn_error_clear(err);
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 "
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));
718 case svn_wc__db_status_deleted:
719 case svn_wc__db_status_not_present:
720 break; /* OK to add */
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,
730 /* Check that the target path is not obstructed, if required. */
733 svn_node_kind_t dst_kind;
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,
745 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
747 scratch_pool, scratch_pool));
749 within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
754 if (move_degraded_to_copy)
755 *move_degraded_to_copy = TRUE;
761 SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
762 cancel_func, cancel_baton,
765 if (src_db_kind == svn_node_file
766 || src_db_kind == svn_node_symlink)
768 err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
770 metadata_only, conflicted, is_move,
771 cancel_func, cancel_baton,
772 notify_func, notify_baton,
778 && src_status == svn_wc__db_status_normal)
780 svn_revnum_t min_rev;
781 svn_revnum_t max_rev;
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) &&
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,
799 if (move_degraded_to_copy)
800 *move_degraded_to_copy = TRUE;
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,
811 if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
812 return svn_error_trace(err);
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 */,
821 /* Run the work queue with the remaining work */
822 SVN_ERR(svn_error_compose_create(
824 svn_wc__wq_run(db, dst_abspath,
825 cancel_func, cancel_baton,
832 /* Public Interface */
835 svn_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,
841 svn_wc_notify_func2_t notify_func,
843 apr_pool_t *scratch_pool)
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),
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,
859 /* Remove the conflict markers of NODE_ABSPATH, that were left over after
860 copying NODE_ABSPATH from SRC_ABSPATH.
862 Only use this function when you know what you're doing. This function
863 explicitly ignores some case insensitivity issues!
867 remove_node_conflict_markers(svn_wc__db_t *db,
868 const char *src_abspath,
869 const char *node_abspath,
870 apr_pool_t *scratch_pool)
872 svn_skel_t *conflict;
874 SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
875 scratch_pool, scratch_pool));
877 /* Do we have conflict markers that should be removed? */
878 if (conflict != NULL)
880 const apr_array_header_t *markers;
882 const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
883 const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
885 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
887 scratch_pool, scratch_pool));
889 /* No iterpool: Maximum number of possible conflict markers is 4 */
890 for (i = 0; markers && (i < markers->nelts); i++)
892 const char *marker_abspath;
893 const char *child_relpath;
894 const char *child_abpath;
896 marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
898 child_relpath = svn_dirent_is_child(src_dir, marker_abspath, NULL);
902 child_abpath = svn_dirent_join(dst_dir, child_relpath,
905 SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, scratch_pool));
913 /* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
914 after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
916 This function doesn't remove the conflict markers on WC_DIR_ABSPATH
919 Only use this function when you know what you're doing. This function
920 explicitly ignores some case insensitivity issues!
923 remove_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)
928 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
930 apr_hash_t *conflicts; /* Unused */
931 apr_hash_index_t *hi;
933 /* Reuse a status helper to obtain all subdirs and conflicts in a single
935 /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
937 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
939 scratch_pool, iterpool));
941 for (hi = apr_hash_first(scratch_pool, nodes);
943 hi = apr_hash_next(hi))
945 const char *name = svn__apr_hash_index_key(hi);
946 struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi);
948 if (info->conflicted)
950 svn_pool_clear(iterpool);
951 SVN_ERR(remove_node_conflict_markers(
953 svn_dirent_join(src_dir_abspath, name, iterpool),
954 svn_dirent_join(wc_dir_abspath, name, iterpool),
957 if (info->kind == svn_node_dir)
959 svn_pool_clear(iterpool);
960 SVN_ERR(remove_all_conflict_markers(
962 svn_dirent_join(src_dir_abspath, name, iterpool),
963 svn_dirent_join(wc_dir_abspath, name, iterpool),
968 svn_pool_destroy(iterpool);
973 svn_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,
980 svn_wc_notify_func2_t notify_func,
982 apr_pool_t *scratch_pool)
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;
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),
993 SVN_ERR(svn_wc__write_check(wc_ctx->db,
994 svn_dirent_dirname(dst_abspath, scratch_pool),
997 SVN_ERR(copy_or_move(&move_degraded_to_copy,
998 wc_ctx, src_abspath, dst_abspath,
999 TRUE /* metadata_only */,
1001 allow_mixed_revisions,
1002 cancel_func, cancel_baton,
1003 notify_func, notify_baton,
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
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
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. */
1021 SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool));
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,
1029 scratch_pool, scratch_pool));
1031 if (kind == svn_node_dir)
1032 SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
1036 SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath,
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,
1047 return SVN_NO_ERROR;