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