copy.c revision 299742
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 */
62static svn_error_t *
63copy_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*/
221static svn_error_t *
222copy_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 */
334static svn_error_t *
335copy_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. */
574static svn_error_t *
575copy_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
909svn_error_t *
910svn_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   */
941static svn_error_t *
942remove_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   */
998static svn_error_t *
999remove_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
1055svn_error_t *
1056svn_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}
1159