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 /* #define RECORD_MIXED_MOVE */
46
47 /*** Code. ***/
48
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.
55
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.
59
60 These values will be used to avoid unneeded work.
61 */
62 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,const svn_io_dirent2_t * dirent,svn_filesize_t recorded_size,apr_time_t recorded_time,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)63 copy_to_tmpdir(svn_skel_t **work_item,
64 svn_node_kind_t *kind,
65 svn_wc__db_t *db,
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,
75 void *cancel_baton,
76 apr_pool_t *result_pool,
77 apr_pool_t *scratch_pool)
78 {
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;
83 if (!kind)
84 kind = &dsk_kind;
85
86 *work_item = NULL;
87
88 if (dirent)
89 {
90 *kind = dirent->kind;
91 is_special = dirent->special;
92 }
93 else
94 SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
95 scratch_pool));
96 if (*kind == svn_node_none)
97 {
98 return SVN_NO_ERROR;
99 }
100 else if (*kind == svn_node_unknown)
101 {
102 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
103 _("Source '%s' is unexpected kind"),
104 svn_dirent_local_style(src_abspath,
105 scratch_pool));
106 }
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;
111
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. */
116
117 if (file_copy && !unversioned)
118 {
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
123 never match. */
124
125 if (dirent
126 && dirent->kind == svn_node_file
127 && recorded_size != SVN_INVALID_FILESIZE
128 && recorded_size == dirent->filesize
129 && recorded_time == dirent->mtime)
130 {
131 modified = FALSE; /* Recorded matches on-disk. Easy out */
132 }
133 else
134 {
135 SVN_ERR(svn_wc__internal_file_modified_p(&modified, db, src_abspath,
136 FALSE, scratch_pool));
137 }
138
139 if (!modified)
140 {
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,
144 TRUE,
145 result_pool, scratch_pool));
146 return SVN_NO_ERROR;
147 }
148 }
149 else if (*kind == svn_node_dir && !file_copy)
150 {
151 /* Just build a new direcory from the workqueue */
152 SVN_ERR(svn_wc__wq_build_dir_install(work_item,
153 db, dst_abspath,
154 result_pool, scratch_pool));
155
156 return SVN_NO_ERROR;
157 }
158
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));
165
166 if (*kind == svn_node_dir)
167 {
168 if (file_copy)
169 SVN_ERR(svn_io_copy_dir_recursively(
170 src_abspath,
171 tmpdir_abspath,
172 svn_dirent_basename(dst_tmp_abspath, scratch_pool),
173 TRUE, /* copy_perms */
174 cancel_func, cancel_baton,
175 scratch_pool));
176 else
177 SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool));
178 }
179 else if (!is_special)
180 SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath,
181 TRUE /* copy_perms */,
182 scratch_pool));
183 else
184 SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool));
185
186 if (file_copy)
187 {
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));
191 }
192
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));
196
197 return SVN_NO_ERROR;
198 }
199
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).
204
205 If IS_MOVE is true, record move information in working copy meta
206 data in addition to copying the file.
207
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.
211
212 This also works for versioned symlinks that are stored in the db as
213 svn_node_file with svn:special set.
214
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.
218
219 These values will be used to avoid unneeded work.
220 */
221 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,const svn_io_dirent2_t * dirent,svn_filesize_t recorded_size,apr_time_t recorded_time,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)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,
234 void *cancel_baton,
235 svn_wc_notify_func2_t notify_func,
236 void *notify_baton,
237 apr_pool_t *scratch_pool)
238 {
239 svn_skel_t *work_items = NULL;
240
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. */
243
244 /* Prepare a temp copy of the filesystem node. It is usually a file, but
245 copy recursively if it's a dir. */
246 if (!metadata_only)
247 {
248 const char *my_src_abspath = NULL;
249 svn_boolean_t handle_as_unversioned = FALSE;
250
251 /* By default, take the copy source as given. */
252 my_src_abspath = src_abspath;
253
254 if (conflicted)
255 {
256 svn_skel_t *conflict;
257 const char *conflict_working;
258 svn_error_t *err;
259
260 /* Is there a text conflict at the source path? */
261 SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
262 db, src_abspath,
263 scratch_pool, scratch_pool));
264
265 err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
266 db, src_abspath, conflict,
267 scratch_pool,
268 scratch_pool);
269
270 if (err && err->apr_err == SVN_ERR_WC_MISSING)
271 {
272 /* not text conflicted */
273 svn_error_clear(err);
274 conflict_working = NULL;
275 }
276 else
277 SVN_ERR(err);
278
279 if (conflict_working)
280 {
281 svn_node_kind_t working_kind;
282
283 /* Does the ".mine" file exist? */
284 SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
285 scratch_pool));
286
287 if (working_kind == svn_node_file)
288 {
289 /* Don't perform unmodified/pristine optimization */
290 handle_as_unversioned = TRUE;
291 my_src_abspath = conflict_working;
292 }
293 }
294 }
295
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));
303 }
304
305 /* Copy the (single) node's metadata, and move the new filesystem node
306 into place. */
307 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
308 dst_op_root_abspath, is_move, work_items,
309 scratch_pool));
310
311 if (notify_func)
312 {
313 svn_wc_notify_t *notify
314 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
315 scratch_pool);
316 notify->kind = svn_node_file;
317
318 (*notify_func)(notify_baton, notify, scratch_pool);
319 }
320 return SVN_NO_ERROR;
321 }
322
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.
329
330 WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
331
332 If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
333 */
334 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,const svn_io_dirent2_t * dirent,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)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,
344 void *cancel_baton,
345 svn_wc_notify_func2_t notify_func,
346 void *notify_baton,
347 apr_pool_t *scratch_pool)
348 {
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;
357
358 /* Prepare a temp copy of the single filesystem node (usually a dir). */
359 if (!metadata_only)
360 {
361 SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind,
362 db, src_abspath, dst_abspath,
363 tmpdir_abspath,
364 FALSE /* file_copy */,
365 FALSE /* unversioned */,
366 dirent, SVN_INVALID_FILESIZE, 0,
367 cancel_func, cancel_baton,
368 scratch_pool, scratch_pool));
369 }
370
371 /* Copy the (single) node's metadata, and move the new filesystem node
372 into place. */
373 SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
374 dst_op_root_abspath, is_move, work_items,
375 scratch_pool));
376
377 if (notify_func)
378 {
379 svn_wc_notify_t *notify
380 = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
381 scratch_pool);
382 notify->kind = svn_node_dir;
383
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));
388
389 (*notify_func)(notify_baton, notify, scratch_pool);
390 }
391
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
395 param. */
396 SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
397 scratch_pool, scratch_pool));
398 else
399 disk_children = NULL;
400
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,
405 db, src_abspath,
406 FALSE /* base_tree_only */,
407 scratch_pool, iterpool));
408 for (hi = apr_hash_first(scratch_pool, versioned_children);
409 hi;
410 hi = apr_hash_next(hi))
411 {
412 const char *child_name, *child_src_abspath, *child_dst_abspath;
413 struct svn_wc__db_info_t *info;
414
415 svn_pool_clear(iterpool);
416
417 if (cancel_func)
418 SVN_ERR(cancel_func(cancel_baton));
419
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);
424
425 if (info->op_root)
426 SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
427 child_src_abspath,
428 child_dst_abspath,
429 is_move,
430 scratch_pool));
431
432 if (info->status == svn_wc__db_status_normal
433 || info->status == svn_wc__db_status_added)
434 {
435 /* We have more work to do than just changing the DB */
436 if (info->kind == svn_node_file)
437 {
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,
442 child_src_abspath,
443 child_dst_abspath,
444 dst_op_root_abspath,
445 tmpdir_abspath,
446 metadata_only, info->conflicted,
447 is_move,
448 disk_children
449 ? svn_hash_gets(disk_children,
450 child_name)
451 : NULL,
452 info->recorded_size,
453 info->recorded_time,
454 cancel_func, cancel_baton,
455 NULL, NULL,
456 iterpool));
457 }
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,
463 disk_children
464 ? svn_hash_gets(disk_children,
465 child_name)
466 : NULL,
467 cancel_func, cancel_baton, NULL, NULL,
468 iterpool));
469 else
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,
473 scratch_pool));
474 }
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)
478 {
479 /* This will be copied as some kind of deletion. Don't touch
480 any actual files */
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));
484
485 /* Don't recurse on children when all we do is creating not-present
486 children */
487 }
488 else if (info->status == svn_wc__db_status_incomplete)
489 {
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,
495 iterpool));
496 }
497 else
498 {
499 SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded);
500
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,
504 iterpool));
505 }
506
507 if (disk_children
508 && (info->status == svn_wc__db_status_normal
509 || info->status == svn_wc__db_status_added))
510 {
511 /* Remove versioned child as it has been handled */
512 svn_hash_sets(disk_children, child_name, NULL);
513 }
514 }
515
516 /* Copy the remaining filesystem children, which are unversioned, skipping
517 any conflict-marker files. */
518 if (disk_children && apr_hash_count(disk_children))
519 {
520 apr_hash_t *marker_files;
521
522 SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
523 src_abspath, scratch_pool,
524 scratch_pool));
525
526 work_items = NULL;
527
528 for (hi = apr_hash_first(scratch_pool, disk_children); hi;
529 hi = apr_hash_next(hi))
530 {
531 const char *name = apr_hash_this_key(hi);
532 const char *unver_src_abspath, *unver_dst_abspath;
533 svn_skel_t *work_item;
534
535 if (svn_wc_is_adm_dir(name, iterpool))
536 continue;
537
538 if (cancel_func)
539 SVN_ERR(cancel_func(cancel_baton));
540
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);
544
545 if (marker_files &&
546 svn_hash_gets(marker_files, unver_src_abspath))
547 continue;
548
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));
555
556 if (work_item)
557 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
558 }
559 SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool));
560 }
561
562 svn_pool_destroy(iterpool);
563
564 return SVN_NO_ERROR;
565 }
566
567
568 /* The guts of svn_wc_copy3() and svn_wc_move().
569 * The additional parameter IS_MOVE indicates whether this is a copy or
570 * a move operation.
571 *
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. */
574 static svn_error_t *
copy_or_move(svn_boolean_t * record_move_on_delete,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)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,
583 void *cancel_baton,
584 svn_wc_notify_func2_t notify_func,
585 void *notify_baton,
586 apr_pool_t *scratch_pool)
587 {
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;
597 svn_error_t *err;
598 svn_filesize_t recorded_size;
599 apr_time_t recorded_time;
600
601 SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
602 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
603
604 dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
605
606 /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
607 throw an error if not. */
608 {
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;
613
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,
620 NULL, NULL,
621 db, src_abspath, scratch_pool, scratch_pool);
622
623 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
624 {
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,
630 scratch_pool));
631 }
632 else
633 SVN_ERR(err);
634
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));
638
639 switch (src_status)
640 {
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,
645 scratch_pool));
646
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,
653 scratch_pool));
654 default:
655 break;
656 }
657
658 if (is_move && ! strcmp(src_abspath, src_wcroot_abspath))
659 {
660 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
661 _("'%s' is the root of a working copy and "
662 "cannot be moved"),
663 svn_dirent_local_style(src_abspath,
664 scratch_pool));
665 }
666 if (is_move && src_repos_relpath && !src_repos_relpath[0])
667 {
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,
672 scratch_pool));
673 }
674
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,
680 NULL, NULL, NULL,
681 db, dstdir_abspath,
682 scratch_pool, scratch_pool);
683
684 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
685 {
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,
691 scratch_pool));
692 }
693 else
694 SVN_ERR(err);
695
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));
699
700 if (!src_repos_root_url)
701 {
702 if (src_status == svn_wc__db_status_added)
703 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
704 &src_repos_root_url,
705 &src_repos_uuid, NULL, NULL, NULL,
706 NULL,
707 db, src_abspath,
708 scratch_pool, scratch_pool));
709 else
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,
712 &src_repos_root_url,
713 &src_repos_uuid, NULL, NULL, NULL,
714 NULL, NULL, NULL, NULL, NULL, NULL,
715 NULL,
716 db, src_abspath,
717 scratch_pool, scratch_pool));
718 }
719
720 if (!dst_repos_root_url)
721 {
722 if (dstdir_status == svn_wc__db_status_added)
723 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
724 &dst_repos_root_url,
725 &dst_repos_uuid, NULL, NULL, NULL,
726 NULL,
727 db, dstdir_abspath,
728 scratch_pool, scratch_pool));
729 else
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,
732 &dst_repos_root_url,
733 &dst_repos_uuid, NULL, NULL, NULL,
734 NULL, NULL, NULL, NULL, NULL, NULL,
735 NULL,
736 db, dstdir_abspath,
737 scratch_pool, scratch_pool));
738 }
739
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'; "
745 "it is from '%s'"),
746 svn_dirent_local_style(dst_abspath, scratch_pool),
747 src_repos_root_url, dst_repos_root_url);
748
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? */
755 }
756
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. */
760 {
761 svn_wc__db_status_t dst_status;
762
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);
768
769 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
770 return svn_error_trace(err);
771
772 svn_error_clear(err);
773
774 if (!err)
775 switch (dst_status)
776 {
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 "
781 "but is excluded."),
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));
788
789 case svn_wc__db_status_deleted:
790 case svn_wc__db_status_not_present:
791 break; /* OK to add */
792
793 default:
794 if (!metadata_only)
795 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
796 _("There is already a versioned item '%s'"),
797 svn_dirent_local_style(dst_abspath,
798 scratch_pool));
799 }
800 }
801
802 /* Check that the target path is not obstructed, if required. */
803 if (!metadata_only)
804 {
805 svn_node_kind_t dst_kind;
806
807 /* (We need only to check the root of the copy, not every path inside
808 copy_versioned_file/_dir.) */
809 SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
810 if (dst_kind != svn_node_none)
811 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
812 _("'%s' already exists and is in the way"),
813 svn_dirent_local_style(dst_abspath,
814 scratch_pool));
815 }
816
817 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
818 dstdir_abspath,
819 scratch_pool, scratch_pool));
820
821 within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
822
823 if (is_move
824 && !within_one_wc)
825 {
826 if (record_move_on_delete)
827 *record_move_on_delete = FALSE;
828
829 is_move = FALSE;
830 }
831
832 if (!within_one_wc)
833 SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
834 cancel_func, cancel_baton,
835 scratch_pool));
836
837 if (src_db_kind == svn_node_file
838 || src_db_kind == svn_node_symlink)
839 {
840 err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
841 tmpdir_abspath,
842 metadata_only, conflicted, is_move,
843 NULL, recorded_size, recorded_time,
844 cancel_func, cancel_baton,
845 notify_func, notify_baton,
846 scratch_pool);
847 }
848 else
849 {
850 if (is_move
851 && src_status == svn_wc__db_status_normal)
852 {
853 svn_revnum_t min_rev;
854 svn_revnum_t max_rev;
855
856 /* Verify that the move source is a single-revision subtree. */
857 SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
858 src_abspath, FALSE, scratch_pool));
859 if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
860 min_rev != max_rev)
861 {
862 if (!allow_mixed_revisions)
863 return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
864 _("Cannot move mixed-revision "
865 "subtree '%s' [%ld:%ld]; "
866 "try updating it first"),
867 svn_dirent_local_style(src_abspath,
868 scratch_pool),
869 min_rev, max_rev);
870
871 #ifndef RECORD_MIXED_MOVE
872 is_move = FALSE;
873 if (record_move_on_delete)
874 *record_move_on_delete = FALSE;
875 #endif
876 }
877 }
878
879 err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
880 tmpdir_abspath, metadata_only, is_move,
881 NULL /* dirent */,
882 cancel_func, cancel_baton,
883 notify_func, notify_baton,
884 scratch_pool);
885 }
886
887 if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
888 return svn_error_trace(err);
889
890 if (is_move)
891 err = svn_error_compose_create(err,
892 svn_wc__db_op_handle_move_back(NULL,
893 db, dst_abspath, src_abspath,
894 NULL /* work_items */,
895 scratch_pool));
896
897 /* Run the work queue with the remaining work */
898 SVN_ERR(svn_error_compose_create(
899 err,
900 svn_wc__wq_run(db, dst_abspath,
901 cancel_func, cancel_baton,
902 scratch_pool)));
903
904 return SVN_NO_ERROR;
905 }
906
907
908 /* Public Interface */
909
910 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)911 svn_wc_copy3(svn_wc_context_t *wc_ctx,
912 const char *src_abspath,
913 const char *dst_abspath,
914 svn_boolean_t metadata_only,
915 svn_cancel_func_t cancel_func,
916 void *cancel_baton,
917 svn_wc_notify_func2_t notify_func,
918 void *notify_baton,
919 apr_pool_t *scratch_pool)
920 {
921 /* Verify that we have the required write lock. */
922 SVN_ERR(svn_wc__write_check(wc_ctx->db,
923 svn_dirent_dirname(dst_abspath, scratch_pool),
924 scratch_pool));
925
926 return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath,
927 metadata_only, FALSE /* is_move */,
928 TRUE /* allow_mixed_revisions */,
929 cancel_func, cancel_baton,
930 notify_func, notify_baton,
931 scratch_pool));
932 }
933
934
935 /* Remove the conflict markers of NODE_ABSPATH, that were left over after
936 copying NODE_ABSPATH from SRC_ABSPATH.
937
938 Only use this function when you know what you're doing. This function
939 explicitly ignores some case insensitivity issues!
940
941 */
942 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)943 remove_node_conflict_markers(svn_wc__db_t *db,
944 const char *src_abspath,
945 const char *node_abspath,
946 apr_pool_t *scratch_pool)
947 {
948 svn_skel_t *conflict;
949
950 SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
951 db, src_abspath,
952 scratch_pool, scratch_pool));
953
954 /* Do we have conflict markers that should be removed? */
955 if (conflict != NULL)
956 {
957 const apr_array_header_t *markers;
958 int i;
959 const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
960 const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
961
962 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
963 conflict,
964 scratch_pool, scratch_pool));
965
966 /* No iterpool: Maximum number of possible conflict markers is 4 */
967 for (i = 0; markers && (i < markers->nelts); i++)
968 {
969 const char *marker_abspath;
970 const char *child_relpath;
971 const char *child_abspath;
972
973 marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
974
975 child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath);
976
977 if (child_relpath)
978 {
979 child_abspath = svn_dirent_join(dst_dir, child_relpath,
980 scratch_pool);
981
982 SVN_ERR(svn_io_remove_file2(child_abspath, TRUE, scratch_pool));
983 }
984 }
985 }
986
987 return SVN_NO_ERROR;
988 }
989
990 /* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
991 after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
992
993 This function doesn't remove the conflict markers on WC_DIR_ABSPATH
994 itself!
995
996 Only use this function when you know what you're doing. This function
997 explicitly ignores some case insensitivity issues!
998 */
999 static svn_error_t *
remove_all_conflict_markers(svn_wc__db_t * db,const char * src_dir_abspath,const char * dst_dir_abspath,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)1000 remove_all_conflict_markers(svn_wc__db_t *db,
1001 const char *src_dir_abspath,
1002 const char *dst_dir_abspath,
1003 svn_cancel_func_t cancel_func,
1004 void *cancel_baton,
1005 apr_pool_t *scratch_pool)
1006 {
1007 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1008 apr_hash_t *nodes;
1009 apr_hash_t *conflicts; /* Unused */
1010 apr_hash_index_t *hi;
1011
1012 /* Reuse a status helper to obtain all subdirs and conflicts in a single
1013 db transaction. */
1014 /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
1015 artillery. */
1016 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
1017 src_dir_abspath,
1018 FALSE /* base_tree_only */,
1019 scratch_pool, iterpool));
1020
1021 for (hi = apr_hash_first(scratch_pool, nodes);
1022 hi;
1023 hi = apr_hash_next(hi))
1024 {
1025 const char *name = apr_hash_this_key(hi);
1026 struct svn_wc__db_info_t *info = apr_hash_this_val(hi);
1027
1028 if (cancel_func)
1029 SVN_ERR(cancel_func(cancel_baton));
1030
1031 if (info->conflicted)
1032 {
1033 svn_pool_clear(iterpool);
1034 SVN_ERR(remove_node_conflict_markers(
1035 db,
1036 svn_dirent_join(src_dir_abspath, name, iterpool),
1037 svn_dirent_join(dst_dir_abspath, name, iterpool),
1038 iterpool));
1039 }
1040 if (info->kind == svn_node_dir)
1041 {
1042 svn_pool_clear(iterpool);
1043 SVN_ERR(remove_all_conflict_markers(
1044 db,
1045 svn_dirent_join(src_dir_abspath, name, iterpool),
1046 svn_dirent_join(dst_dir_abspath, name, iterpool),
1047 cancel_func, cancel_baton,
1048 iterpool));
1049 }
1050 }
1051
1052 svn_pool_destroy(iterpool);
1053 return SVN_NO_ERROR;
1054 }
1055
1056 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)1057 svn_wc__move2(svn_wc_context_t *wc_ctx,
1058 const char *src_abspath,
1059 const char *dst_abspath,
1060 svn_boolean_t metadata_only,
1061 svn_boolean_t allow_mixed_revisions,
1062 svn_cancel_func_t cancel_func,
1063 void *cancel_baton,
1064 svn_wc_notify_func2_t notify_func,
1065 void *notify_baton,
1066 apr_pool_t *scratch_pool)
1067 {
1068 svn_wc__db_t *db = wc_ctx->db;
1069 svn_boolean_t record_on_delete = TRUE;
1070 svn_node_kind_t kind;
1071 svn_boolean_t conflicted;
1072
1073 /* Verify that we have the required write locks. */
1074 SVN_ERR(svn_wc__write_check(wc_ctx->db,
1075 svn_dirent_dirname(src_abspath, scratch_pool),
1076 scratch_pool));
1077 SVN_ERR(svn_wc__write_check(wc_ctx->db,
1078 svn_dirent_dirname(dst_abspath, scratch_pool),
1079 scratch_pool));
1080
1081 SVN_ERR(copy_or_move(&record_on_delete,
1082 wc_ctx, src_abspath, dst_abspath,
1083 TRUE /* metadata_only */,
1084 TRUE /* is_move */,
1085 allow_mixed_revisions,
1086 cancel_func, cancel_baton,
1087 notify_func, notify_baton,
1088 scratch_pool));
1089
1090 /* An interrupt at this point will leave the new copy marked as
1091 moved-here but the source has not yet been deleted or marked as
1092 moved-to. */
1093
1094 /* Should we be using a workqueue for this move? It's not clear.
1095 What should happen if the copy above is interrupted? The user
1096 may want to abort the move and a workqueue might interfere with
1097 that.
1098
1099 BH: On Windows it is not unlikely to encounter an access denied on
1100 this line. Installing the move in the workqueue via the copy_or_move
1101 might make it hard to recover from that situation, while the DB
1102 is still in a valid state. So be careful when switching this over
1103 to the workqueue. */
1104 if (!metadata_only)
1105 {
1106 svn_error_t *err;
1107
1108 err = svn_error_trace(svn_io_file_rename2(src_abspath, dst_abspath,
1109 FALSE, scratch_pool));
1110
1111 /* Let's try if we can keep wc.db consistent even when the move
1112 fails. Deleting the target is a wc.db only operation, while
1113 going forward (delaying the error) would try to change
1114 conflict markers, which might also fail. */
1115 if (err)
1116 return svn_error_trace(
1117 svn_error_compose_create(
1118 err,
1119 svn_wc__db_op_delete(wc_ctx->db, dst_abspath, NULL, TRUE,
1120 NULL, NULL, cancel_func, cancel_baton,
1121 NULL, NULL,
1122 scratch_pool)));
1123 }
1124
1125 SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
1126 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1127 NULL, NULL, NULL, NULL, NULL, NULL,
1128 &conflicted, NULL, NULL, NULL,
1129 NULL, NULL, NULL,
1130 db, src_abspath,
1131 scratch_pool, scratch_pool));
1132
1133 if (kind == svn_node_dir)
1134 SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
1135 cancel_func, cancel_baton,
1136 scratch_pool));
1137
1138 if (conflicted)
1139 {
1140 /* When we moved a directory, we moved the conflict markers
1141 with the target... if we moved a file we only moved the
1142 file itself and the markers are still in the old location */
1143 SVN_ERR(remove_node_conflict_markers(db, src_abspath,
1144 (kind == svn_node_dir)
1145 ? dst_abspath
1146 : src_abspath,
1147 scratch_pool));
1148 }
1149
1150 SVN_ERR(svn_wc__db_op_delete(db, src_abspath,
1151 record_on_delete ? dst_abspath : NULL,
1152 TRUE /* delete_dir_externals */,
1153 NULL /* conflict */, NULL /* work_items */,
1154 cancel_func, cancel_baton,
1155 notify_func, notify_baton,
1156 scratch_pool));
1157
1158 return SVN_NO_ERROR;
1159 }
1160