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