]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_wc/copy.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_wc / copy.c
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 \f
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 \f
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 *
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 *
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 *
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 *
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             return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
795                                _("There is already a versioned item '%s'"),
796                                svn_dirent_local_style(dst_abspath,
797                                                       scratch_pool));
798         }
799   }
800
801   /* Check that the target path is not obstructed, if required. */
802   if (!metadata_only)
803     {
804       svn_node_kind_t dst_kind;
805
806       /* (We need only to check the root of the copy, not every path inside
807          copy_versioned_file/_dir.) */
808       SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
809       if (dst_kind != svn_node_none)
810         return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
811                                  _("'%s' already exists and is in the way"),
812                                  svn_dirent_local_style(dst_abspath,
813                                                         scratch_pool));
814     }
815
816   SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
817                                          dstdir_abspath,
818                                          scratch_pool, scratch_pool));
819
820   within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
821
822   if (is_move
823       && !within_one_wc)
824     {
825       if (record_move_on_delete)
826         *record_move_on_delete = FALSE;
827
828       is_move = FALSE;
829     }
830
831   if (!within_one_wc)
832     SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
833                                          cancel_func, cancel_baton,
834                                          scratch_pool));
835
836   if (src_db_kind == svn_node_file
837       || src_db_kind == svn_node_symlink)
838     {
839       err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
840                                 tmpdir_abspath,
841                                 metadata_only, conflicted, is_move,
842                                 NULL, recorded_size, recorded_time,
843                                 cancel_func, cancel_baton,
844                                 notify_func, notify_baton,
845                                 scratch_pool);
846     }
847   else
848     {
849       if (is_move
850           && src_status == svn_wc__db_status_normal)
851         {
852           svn_revnum_t min_rev;
853           svn_revnum_t max_rev;
854
855           /* Verify that the move source is a single-revision subtree. */
856           SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
857                                                src_abspath, FALSE, scratch_pool));
858           if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
859               min_rev != max_rev)
860             {
861               if (!allow_mixed_revisions)
862                 return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
863                                          _("Cannot move mixed-revision "
864                                            "subtree '%s' [%ld:%ld]; "
865                                            "try updating it first"),
866                                          svn_dirent_local_style(src_abspath,
867                                                                 scratch_pool),
868                                          min_rev, max_rev);
869
870 #ifndef RECORD_MIXED_MOVE
871               is_move = FALSE;
872               if (record_move_on_delete)
873                 *record_move_on_delete = FALSE;
874 #endif
875             }
876         }
877
878       err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
879                                tmpdir_abspath, metadata_only, is_move,
880                                NULL /* dirent */,
881                                cancel_func, cancel_baton,
882                                notify_func, notify_baton,
883                                scratch_pool);
884     }
885
886   if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
887     return svn_error_trace(err);
888
889   if (is_move)
890     err = svn_error_compose_create(err,
891                 svn_wc__db_op_handle_move_back(NULL,
892                                                db, dst_abspath, src_abspath,
893                                                NULL /* work_items */,
894                                                scratch_pool));
895
896   /* Run the work queue with the remaining work */
897   SVN_ERR(svn_error_compose_create(
898                                 err,
899                                 svn_wc__wq_run(db, dst_abspath,
900                                                    cancel_func, cancel_baton,
901                                                    scratch_pool)));
902
903   return SVN_NO_ERROR;
904 }
905
906
907 /* Public Interface */
908
909 svn_error_t *
910 svn_wc_copy3(svn_wc_context_t *wc_ctx,
911              const char *src_abspath,
912              const char *dst_abspath,
913              svn_boolean_t metadata_only,
914              svn_cancel_func_t cancel_func,
915              void *cancel_baton,
916              svn_wc_notify_func2_t notify_func,
917              void *notify_baton,
918              apr_pool_t *scratch_pool)
919 {
920   /* Verify that we have the required write lock. */
921   SVN_ERR(svn_wc__write_check(wc_ctx->db,
922                               svn_dirent_dirname(dst_abspath, scratch_pool),
923                               scratch_pool));
924
925   return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath,
926                                       metadata_only, FALSE /* is_move */,
927                                       TRUE /* allow_mixed_revisions */,
928                                       cancel_func, cancel_baton,
929                                       notify_func, notify_baton,
930                                       scratch_pool));
931 }
932
933
934 /* Remove the conflict markers of NODE_ABSPATH, that were left over after
935    copying NODE_ABSPATH from SRC_ABSPATH.
936
937    Only use this function when you know what you're doing. This function
938    explicitly ignores some case insensitivity issues!
939
940    */
941 static svn_error_t *
942 remove_node_conflict_markers(svn_wc__db_t *db,
943                              const char *src_abspath,
944                              const char *node_abspath,
945                              apr_pool_t *scratch_pool)
946 {
947   svn_skel_t *conflict;
948
949   SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
950                                    db, src_abspath,
951                                    scratch_pool, scratch_pool));
952
953   /* Do we have conflict markers that should be removed? */
954   if (conflict != NULL)
955     {
956       const apr_array_header_t *markers;
957       int i;
958       const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
959       const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
960
961       SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
962                                             conflict,
963                                             scratch_pool, scratch_pool));
964
965       /* No iterpool: Maximum number of possible conflict markers is 4 */
966       for (i = 0; markers && (i < markers->nelts); i++)
967         {
968           const char *marker_abspath;
969           const char *child_relpath;
970           const char *child_abspath;
971
972           marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
973
974           child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath);
975
976           if (child_relpath)
977             {
978               child_abspath = svn_dirent_join(dst_dir, child_relpath,
979                                               scratch_pool);
980
981               SVN_ERR(svn_io_remove_file2(child_abspath, TRUE, scratch_pool));
982             }
983         }
984     }
985
986   return SVN_NO_ERROR;
987 }
988
989 /* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
990    after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
991
992    This function doesn't remove the conflict markers on WC_DIR_ABSPATH
993    itself!
994
995    Only use this function when you know what you're doing. This function
996    explicitly ignores some case insensitivity issues!
997    */
998 static svn_error_t *
999 remove_all_conflict_markers(svn_wc__db_t *db,
1000                             const char *src_dir_abspath,
1001                             const char *dst_dir_abspath,
1002                             svn_cancel_func_t cancel_func,
1003                             void *cancel_baton,
1004                             apr_pool_t *scratch_pool)
1005 {
1006   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1007   apr_hash_t *nodes;
1008   apr_hash_t *conflicts; /* Unused */
1009   apr_hash_index_t *hi;
1010
1011   /* Reuse a status helper to obtain all subdirs and conflicts in a single
1012      db transaction. */
1013   /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
1014           artillery. */
1015   SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
1016                                         src_dir_abspath,
1017                                         FALSE /* base_tree_only */,
1018                                         scratch_pool, iterpool));
1019
1020   for (hi = apr_hash_first(scratch_pool, nodes);
1021        hi;
1022        hi = apr_hash_next(hi))
1023     {
1024       const char *name = apr_hash_this_key(hi);
1025       struct svn_wc__db_info_t *info = apr_hash_this_val(hi);
1026
1027       if (cancel_func)
1028         SVN_ERR(cancel_func(cancel_baton));
1029
1030       if (info->conflicted)
1031         {
1032           svn_pool_clear(iterpool);
1033           SVN_ERR(remove_node_conflict_markers(
1034                             db,
1035                             svn_dirent_join(src_dir_abspath, name, iterpool),
1036                             svn_dirent_join(dst_dir_abspath, name, iterpool),
1037                             iterpool));
1038         }
1039       if (info->kind == svn_node_dir)
1040         {
1041           svn_pool_clear(iterpool);
1042           SVN_ERR(remove_all_conflict_markers(
1043                             db,
1044                             svn_dirent_join(src_dir_abspath, name, iterpool),
1045                             svn_dirent_join(dst_dir_abspath, name, iterpool),
1046                             cancel_func, cancel_baton,
1047                             iterpool));
1048         }
1049     }
1050
1051   svn_pool_destroy(iterpool);
1052   return SVN_NO_ERROR;
1053 }
1054
1055 svn_error_t *
1056 svn_wc__move2(svn_wc_context_t *wc_ctx,
1057               const char *src_abspath,
1058               const char *dst_abspath,
1059               svn_boolean_t metadata_only,
1060               svn_boolean_t allow_mixed_revisions,
1061               svn_cancel_func_t cancel_func,
1062               void *cancel_baton,
1063               svn_wc_notify_func2_t notify_func,
1064               void *notify_baton,
1065               apr_pool_t *scratch_pool)
1066 {
1067   svn_wc__db_t *db = wc_ctx->db;
1068   svn_boolean_t record_on_delete = TRUE;
1069   svn_node_kind_t kind;
1070   svn_boolean_t conflicted;
1071
1072   /* Verify that we have the required write locks. */
1073   SVN_ERR(svn_wc__write_check(wc_ctx->db,
1074                               svn_dirent_dirname(src_abspath, scratch_pool),
1075                               scratch_pool));
1076   SVN_ERR(svn_wc__write_check(wc_ctx->db,
1077                               svn_dirent_dirname(dst_abspath, scratch_pool),
1078                               scratch_pool));
1079
1080   SVN_ERR(copy_or_move(&record_on_delete,
1081                        wc_ctx, src_abspath, dst_abspath,
1082                        TRUE /* metadata_only */,
1083                        TRUE /* is_move */,
1084                        allow_mixed_revisions,
1085                        cancel_func, cancel_baton,
1086                        notify_func, notify_baton,
1087                        scratch_pool));
1088
1089   /* An interrupt at this point will leave the new copy marked as
1090      moved-here but the source has not yet been deleted or marked as
1091      moved-to. */
1092
1093   /* Should we be using a workqueue for this move?  It's not clear.
1094      What should happen if the copy above is interrupted?  The user
1095      may want to abort the move and a workqueue might interfere with
1096      that.
1097
1098      BH: On Windows it is not unlikely to encounter an access denied on
1099      this line. Installing the move in the workqueue via the copy_or_move
1100      might make it hard to recover from that situation, while the DB
1101      is still in a valid state. So be careful when switching this over
1102      to the workqueue. */
1103   if (!metadata_only)
1104     {
1105       svn_error_t *err;
1106
1107       err = svn_error_trace(svn_io_file_rename(src_abspath, dst_abspath,
1108                                                scratch_pool));
1109
1110       /* Let's try if we can keep wc.db consistent even when the move
1111          fails. Deleting the target is a wc.db only operation, while
1112          going forward (delaying the error) would try to change
1113          conflict markers, which might also fail. */
1114       if (err)
1115         return svn_error_trace(
1116           svn_error_compose_create(
1117               err,
1118               svn_wc__db_op_delete(wc_ctx->db, dst_abspath, NULL, TRUE,
1119                                    NULL, NULL, cancel_func, cancel_baton,
1120                                    NULL, NULL,
1121                                    scratch_pool)));
1122     }
1123
1124   SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
1125                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1126                                NULL, NULL, NULL, NULL, NULL, NULL,
1127                                &conflicted, NULL, NULL, NULL,
1128                                NULL, NULL, NULL,
1129                                db, src_abspath,
1130                                scratch_pool, scratch_pool));
1131
1132   if (kind == svn_node_dir)
1133     SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
1134                                         cancel_func, cancel_baton,
1135                                         scratch_pool));
1136
1137   if (conflicted)
1138     {
1139       /* When we moved a directory, we moved the conflict markers
1140          with the target... if we moved a file we only moved the
1141          file itself and the markers are still in the old location */
1142       SVN_ERR(remove_node_conflict_markers(db, src_abspath,
1143                                            (kind == svn_node_dir)
1144                                              ? dst_abspath
1145                                              : src_abspath,
1146                                            scratch_pool));
1147     }
1148
1149   SVN_ERR(svn_wc__db_op_delete(db, src_abspath,
1150                                record_on_delete ? dst_abspath : NULL,
1151                                TRUE /* delete_dir_externals */,
1152                                NULL /* conflict */, NULL /* work_items */,
1153                                cancel_func, cancel_baton,
1154                                notify_func, notify_baton,
1155                                scratch_pool));
1156
1157   return SVN_NO_ERROR;
1158 }