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