The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * copy.c:  wc 'copy' functionality.
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

/* ==================================================================== */



/*** Includes. ***/

#include <string.h>
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"

#include "wc.h"
#include "workqueue.h"
#include "adm_files.h"
#include "props.h"
#include "translate.h"
#include "entries.h"

#include "svn_private_config.h"
#include "private/svn_wc_private.h"


/*** Code. ***/

/* Make a copy of the filesystem node (or tree if RECURSIVE) at
   SRC_ABSPATH under a temporary name in the directory
   TMPDIR_ABSPATH and return the absolute path of the copy in
   *DST_ABSPATH.  Return the node kind of SRC_ABSPATH in *KIND.  If
   SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate
   that no copy was made. */
static svn_error_t *
copy_to_tmpdir(const char **dst_abspath,
               svn_node_kind_t *kind,
               const char *src_abspath,
               const char *tmpdir_abspath,
               svn_boolean_t recursive,
               svn_cancel_func_t cancel_func,
               void *cancel_baton,
               apr_pool_t *scratch_pool)
{
  svn_boolean_t is_special;
  svn_io_file_del_t delete_when;

  SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
                                    scratch_pool));
  if (*kind == svn_node_none)
    {
      *dst_abspath = NULL;
      return SVN_NO_ERROR;
    }
  else if (*kind == svn_node_unknown)
    {
      return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
                               _("Source '%s' is unexpected kind"),
                               svn_dirent_local_style(src_abspath,
                                                      scratch_pool));
    }
  else if (*kind == svn_node_dir || is_special)
    delete_when = svn_io_file_del_on_close;
  else /* the default case: (*kind == svn_node_file) */
    delete_when = svn_io_file_del_none;

  /* ### Do we need a pool cleanup to remove the copy?  We can't use
     ### svn_io_file_del_on_pool_cleanup above because a) it won't
     ### handle the directory case and b) we need to be able to remove
     ### the cleanup before queueing the move work item. */

  /* Set DST_ABSPATH to a temporary unique path.  If *KIND is file, leave a
     file there and then overwrite it; otherwise leave no node on disk at
     that path.  In the latter case, something else might use that path
     before we get around to using it a moment later, but never mind. */
  SVN_ERR(svn_io_open_unique_file3(NULL, dst_abspath, tmpdir_abspath,
                                   delete_when, scratch_pool, scratch_pool));

  if (*kind == svn_node_dir)
    {
      if (recursive)
        SVN_ERR(svn_io_copy_dir_recursively(src_abspath,
                                            tmpdir_abspath,
                                            svn_dirent_basename(*dst_abspath,
                                                                scratch_pool),
                                            TRUE, /* copy_perms */
                                            cancel_func, cancel_baton,
                                            scratch_pool));
      else
        SVN_ERR(svn_io_dir_make(*dst_abspath, APR_OS_DEFAULT, scratch_pool));
    }
  else if (!is_special)
    SVN_ERR(svn_io_copy_file(src_abspath, *dst_abspath, TRUE, /* copy_perms */
                             scratch_pool));
  else
    SVN_ERR(svn_io_copy_link(src_abspath, *dst_abspath, scratch_pool));


  return SVN_NO_ERROR;
}


/* If SRC_ABSPATH and DST_ABSPATH use different pristine stores, copy the
   pristine text of SRC_ABSPATH (if there is one) into the pristine text
   store connected to DST_ABSPATH.  This will only happen when copying into
   a separate WC such as an external directory.
 */
static svn_error_t *
copy_pristine_text_if_necessary(svn_wc__db_t *db,
                                const char *src_abspath,
                                const char *dst_abspath,
                                const char *tmpdir_abspath,
                                const svn_checksum_t *checksum,
                                svn_cancel_func_t cancel_func,
                                void *cancel_baton,
                                apr_pool_t *scratch_pool)
{
  svn_boolean_t present;
  svn_stream_t *src_pristine, *tmp_pristine;
  const char *tmp_pristine_abspath;
  const svn_checksum_t *sha1_checksum, *md5_checksum;

  SVN_ERR_ASSERT(checksum->kind == svn_checksum_sha1);

  /* If it's already in DST_ABSPATH's pristine store, we're done. */
  SVN_ERR(svn_wc__db_pristine_check(&present, db, dst_abspath, checksum,
                                    scratch_pool));
  if (present)
    return SVN_NO_ERROR;

  sha1_checksum = checksum;
  SVN_ERR(svn_wc__db_pristine_get_md5(&md5_checksum, db,
                                      src_abspath, checksum,
                                      scratch_pool, scratch_pool));

  SVN_ERR(svn_wc__db_pristine_read(&src_pristine, NULL, db,
                                   src_abspath, sha1_checksum,
                                   scratch_pool, scratch_pool));
  SVN_ERR(svn_stream_open_unique(&tmp_pristine, &tmp_pristine_abspath,
                                 tmpdir_abspath, svn_io_file_del_none,
                                 scratch_pool, scratch_pool));
  SVN_ERR(svn_stream_copy3(src_pristine, tmp_pristine,
                           cancel_func, cancel_baton,
                           scratch_pool));
  SVN_ERR(svn_wc__db_pristine_install(db, tmp_pristine_abspath,
                                      sha1_checksum, md5_checksum,
                                      scratch_pool));

  return SVN_NO_ERROR;
}


/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB.
   If METADATA_ONLY is true, copy only the versioned metadata,
   otherwise copy both the versioned metadata and the filesystem node (even
   if it is the wrong kind, and recursively if it is a dir).

   If the versioned file has a text conflict, and the .mine file exists in
   the filesystem, copy the .mine file to DST_ABSPATH.  Otherwise, copy the
   versioned file itself.

   This also works for versioned symlinks that are stored in the db as
   svn_wc__db_kind_file with svn:special set. */
static svn_error_t *
copy_versioned_file(svn_wc__db_t *db,
                    const char *src_abspath,
                    const char *dst_abspath,
                    const char *dst_op_root_abspath,
                    const char *tmpdir_abspath,
                    const svn_checksum_t *checksum,
                    svn_boolean_t metadata_only,
                    svn_boolean_t conflicted,
                    svn_cancel_func_t cancel_func,
                    void *cancel_baton,
                    svn_wc_notify_func2_t notify_func,
                    void *notify_baton,
                    apr_pool_t *scratch_pool)
{
  svn_skel_t *work_items = NULL;
  const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);

  /* In case we are copying from one WC to another (e.g. an external dir),
     ensure the destination WC has a copy of the pristine text. */

  /* Checksum is NULL for local additions */
  if (checksum != NULL)
    SVN_ERR(copy_pristine_text_if_necessary(db, src_abspath, dst_abspath,
                                            tmpdir_abspath, checksum,
                                            cancel_func, cancel_baton,
                                            scratch_pool));

  /* Prepare a temp copy of the filesystem node.  It is usually a file, but
     copy recursively if it's a dir. */
  if (!metadata_only)
    {
      const char *tmp_dst_abspath;
      svn_node_kind_t disk_kind;
      const char *my_src_abspath = NULL;
      int i;

      /* By default, take the copy source as given. */
      my_src_abspath = src_abspath;

      if (conflicted)
        {
          const apr_array_header_t *conflicts;
          const char *conflict_working = NULL;

          /* Is there a text conflict at the source path? */
          SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, src_abspath,
                                            scratch_pool, scratch_pool));

          for (i = 0; i < conflicts->nelts; i++)
            {
              const svn_wc_conflict_description2_t *desc;

              desc = APR_ARRAY_IDX(conflicts, i,
                                   const svn_wc_conflict_description2_t*);

              if (desc->kind == svn_wc_conflict_kind_text)
                {
                  conflict_working = desc->my_abspath;
                  break;
                }
            }

          if (conflict_working)
            {
              svn_node_kind_t working_kind;

              /* Does the ".mine" file exist? */
              SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
                                        scratch_pool));

              if (working_kind == svn_node_file)
                my_src_abspath = conflict_working;
            }
        }

      SVN_ERR(copy_to_tmpdir(&tmp_dst_abspath, &disk_kind, my_src_abspath,
                             tmpdir_abspath,
                             TRUE, /* recursive */
                             cancel_func, cancel_baton, scratch_pool));

      if (tmp_dst_abspath)
        {
          svn_skel_t *work_item;

          /* Remove 'read-only' from the destination file; it's a local add. */
            {
              const svn_string_t *needs_lock;
              SVN_ERR(svn_wc__internal_propget(&needs_lock, db, src_abspath,
                                               SVN_PROP_NEEDS_LOCK,
                                               scratch_pool, scratch_pool));
              if (needs_lock)
                SVN_ERR(svn_io_set_file_read_write(tmp_dst_abspath,
                                                   FALSE, scratch_pool));
            }

          SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, dir_abspath,
                                             tmp_dst_abspath, dst_abspath,
                                             scratch_pool, scratch_pool));
          work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);

          if (disk_kind == svn_node_file)
            {
              svn_boolean_t modified;

              /* It's faster to look for mods on the source now, as
                 the timestamp might match, than to examine the
                 destination later as the destination timestamp will
                 never match. */
              SVN_ERR(svn_wc__internal_file_modified_p(&modified,
                                                       db, src_abspath,
                                                       FALSE, scratch_pool));
              if (!modified)
                {
                  SVN_ERR(svn_wc__wq_build_record_fileinfo(&work_item,
                                                           db, dst_abspath, 0,
                                                           scratch_pool,
                                                           scratch_pool));
                  work_items = svn_wc__wq_merge(work_items, work_item,
                                                scratch_pool);
                }
            }
        }
    }

  /* Copy the (single) node's metadata, and move the new filesystem node
     into place. */
  SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, dst_op_root_abspath,
                             work_items, scratch_pool));
  SVN_ERR(svn_wc__wq_run(db, dir_abspath,
                         cancel_func, cancel_baton, scratch_pool));

  if (notify_func)
    {
      svn_wc_notify_t *notify
        = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
                               scratch_pool);
      notify->kind = svn_node_file;
      (*notify_func)(notify_baton, notify, scratch_pool);
    }
  return SVN_NO_ERROR;
}

/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB,
   recursively.  If METADATA_ONLY is true, copy only the versioned metadata,
   otherwise copy both the versioned metadata and the filesystem nodes (even
   if they are the wrong kind, and including unversioned children). */
static svn_error_t *
copy_versioned_dir(svn_wc__db_t *db,
                   const char *src_abspath,
                   const char *dst_abspath,
                   const char *dst_op_root_abspath,
                   const char *tmpdir_abspath,
                   svn_boolean_t metadata_only,
                   svn_cancel_func_t cancel_func,
                   void *cancel_baton,
                   svn_wc_notify_func2_t notify_func,
                   void *notify_baton,
                   apr_pool_t *scratch_pool)
{
  svn_skel_t *work_items = NULL;
  const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
  const apr_array_header_t *versioned_children;
  apr_hash_t *disk_children;
  svn_node_kind_t disk_kind;
  apr_pool_t *iterpool;
  int i;

  /* Prepare a temp copy of the single filesystem node (usually a dir). */
  if (!metadata_only)
    {
      const char *tmp_dst_abspath;

      SVN_ERR(copy_to_tmpdir(&tmp_dst_abspath, &disk_kind, src_abspath,
                             tmpdir_abspath, FALSE, /* recursive */
                             cancel_func, cancel_baton, scratch_pool));
      if (tmp_dst_abspath)
        {
          svn_skel_t *work_item;

          SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, dir_abspath,
                                             tmp_dst_abspath, dst_abspath,
                                             scratch_pool, scratch_pool));
          work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
        }
    }

  /* Copy the (single) node's metadata, and move the new filesystem node
     into place. */
  SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, dst_op_root_abspath,
                             work_items, scratch_pool));
  SVN_ERR(svn_wc__wq_run(db, dir_abspath,
                         cancel_func, cancel_baton, scratch_pool));

  if (notify_func)
    {
      svn_wc_notify_t *notify
        = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
                               scratch_pool);
      notify->kind = svn_node_dir;
      (*notify_func)(notify_baton, notify, scratch_pool);
    }

  if (!metadata_only && disk_kind == svn_node_dir)
    /* All filesystem children, versioned and unversioned.  We're only
       interested in their names, so we can pass TRUE as the only_check_type
       param. */
    SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
                                scratch_pool, scratch_pool));
  else
    disk_children = NULL;

  /* Copy all the versioned children */
  SVN_ERR(svn_wc__db_read_children(&versioned_children, db, src_abspath,
                                   scratch_pool, scratch_pool));
  iterpool = svn_pool_create(scratch_pool);
  for (i = 0; i < versioned_children->nelts; ++i)
    {
      const char *child_name, *child_src_abspath, *child_dst_abspath;
      svn_wc__db_status_t child_status;
      svn_wc__db_kind_t child_kind;
      svn_boolean_t op_root;
      svn_boolean_t conflicted;
      const svn_checksum_t *checksum;

      svn_pool_clear(iterpool);
      if (cancel_func)
        SVN_ERR(cancel_func(cancel_baton));

      child_name = APR_ARRAY_IDX(versioned_children, i, const char *);
      child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool);
      child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool);

      SVN_ERR(svn_wc__db_read_info(&child_status, &child_kind, NULL, NULL,
                                   NULL, NULL, NULL, NULL, NULL, NULL,
                                   &checksum, NULL, NULL, NULL, NULL, NULL,
                                   NULL, NULL, NULL, NULL, &conflicted,
                                   &op_root, NULL, NULL, NULL, NULL, NULL,
                                   db, child_src_abspath,
                                   iterpool, iterpool));

      if (op_root)
        SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
                                                  child_src_abspath,
                                                  child_dst_abspath,
                                                  scratch_pool));

      if (child_status == svn_wc__db_status_normal
          || child_status == svn_wc__db_status_added)
        {
          /* We have more work to do than just changing the DB */
          if (child_kind == svn_wc__db_kind_file)
            {
              svn_boolean_t skip = FALSE;

              /* We should skip this node if this child is a file external
                 (issues #3589, #4000) */
              if (child_status == svn_wc__db_status_normal)
                {
                  SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL,
                                                   NULL, NULL, NULL, NULL,
                                                   NULL, NULL, NULL, NULL,
                                                   NULL, NULL, &skip,
                                                   db, child_src_abspath,
                                                   scratch_pool,
                                                   scratch_pool));
                }

              if (!skip)
                SVN_ERR(copy_versioned_file(db,
                                            child_src_abspath,
                                            child_dst_abspath,
                                            dst_op_root_abspath,
                                            tmpdir_abspath, checksum,
                                            metadata_only, conflicted,
                                            cancel_func, cancel_baton,
                                            NULL, NULL,
                                            iterpool));
            }
          else if (child_kind == svn_wc__db_kind_dir)
            SVN_ERR(copy_versioned_dir(db,
                                       child_src_abspath, child_dst_abspath,
                                       dst_op_root_abspath, tmpdir_abspath,
                                       metadata_only,
                                       cancel_func, cancel_baton, NULL, NULL,
                                       iterpool));
          else
            return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
                                     _("cannot handle node kind for '%s'"),
                                     svn_dirent_local_style(child_src_abspath,
                                                            scratch_pool));
        }
      else if (child_status == svn_wc__db_status_deleted
          || child_status == svn_wc__db_status_not_present
          || child_status == svn_wc__db_status_excluded)
        {
          /* This will be copied as some kind of deletion. Don't touch
             any actual files */
          SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, child_dst_abspath,
                                     dst_op_root_abspath,
                                     NULL, iterpool));

          /* Don't recurse on children while all we do is creating not-present
             children */
        }
      else
        {
          SVN_ERR_ASSERT(child_status == svn_wc__db_status_server_excluded);

          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                                   _("Cannot copy '%s' excluded by server"),
                                   svn_dirent_local_style(src_abspath,
                                                          iterpool));
        }

      if (disk_children
          && (child_status == svn_wc__db_status_normal
              || child_status == svn_wc__db_status_added))
        {
          /* Remove versioned child as it has been handled */
          apr_hash_set(disk_children, child_name, APR_HASH_KEY_STRING, NULL);
        }
    }

  /* Copy the remaining filesystem children, which are unversioned, skipping
     any conflict-marker files. */
  if (disk_children)
    {
      apr_hash_index_t *hi;
      apr_hash_t *marker_files;

      SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
                                                   src_abspath, scratch_pool,
                                                   scratch_pool));

      for (hi = apr_hash_first(scratch_pool, disk_children); hi;
           hi = apr_hash_next(hi))
        {
          const char *name = svn__apr_hash_index_key(hi);
          const char *unver_src_abspath, *unver_dst_abspath;
          const char *tmp_dst_abspath;

          if (svn_wc_is_adm_dir(name, iterpool))
            continue;

          if (marker_files &&
              apr_hash_get(marker_files, name, APR_HASH_KEY_STRING))
            continue;

          svn_pool_clear(iterpool);
          if (cancel_func)
            SVN_ERR(cancel_func(cancel_baton));

          unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool);
          unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool);

          SVN_ERR(copy_to_tmpdir(&tmp_dst_abspath, &disk_kind,
                                 unver_src_abspath, tmpdir_abspath,
                                 TRUE, /* recursive */
                                 cancel_func, cancel_baton, iterpool));
          if (tmp_dst_abspath)
            {
              svn_skel_t *work_item;
              SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, dir_abspath,
                                                 tmp_dst_abspath,
                                                 unver_dst_abspath,
                                                 iterpool, iterpool));
              SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_item,
                                        iterpool));
            }

        }
      SVN_ERR(svn_wc__wq_run(db, dst_abspath, cancel_func, cancel_baton,
                             scratch_pool));
    }

  svn_pool_destroy(iterpool);

  return SVN_NO_ERROR;
}


/* Public Interface */

svn_error_t *
svn_wc_copy3(svn_wc_context_t *wc_ctx,
             const char *src_abspath,
             const char *dst_abspath,
             svn_boolean_t metadata_only,
             svn_cancel_func_t cancel_func,
             void *cancel_baton,
             svn_wc_notify_func2_t notify_func,
             void *notify_baton,
             apr_pool_t *scratch_pool)
{
  svn_wc__db_t *db = wc_ctx->db;
  svn_wc__db_kind_t src_db_kind;
  const char *dstdir_abspath;
  svn_boolean_t conflicted;
  const svn_checksum_t *checksum;
  const char *tmpdir_abspath;

  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));

  dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);

  /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
     throw an error if not. */
  {
    svn_wc__db_status_t src_status, dstdir_status;
    const char *src_repos_root_url, *dst_repos_root_url;
    const char *src_repos_uuid, *dst_repos_uuid;
    svn_error_t *err;

    err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL, NULL,
                               &src_repos_root_url, &src_repos_uuid, NULL,
                               NULL, NULL, NULL, &checksum, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
                               NULL, NULL, NULL, NULL, NULL, NULL,
                               db, src_abspath, scratch_pool, scratch_pool);

    if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
      {
        /* Replicate old error code and text */
        svn_error_clear(err);
        return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
                                 _("'%s' is not under version control"),
                                 svn_dirent_local_style(src_abspath,
                                                        scratch_pool));
      }
    else
      SVN_ERR(err);

    switch (src_status)
      {
        case svn_wc__db_status_deleted:
          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                                   _("Deleted node '%s' can't be copied."),
                                   svn_dirent_local_style(src_abspath,
                                                          scratch_pool));

        case svn_wc__db_status_excluded:
        case svn_wc__db_status_server_excluded:
        case svn_wc__db_status_not_present:
          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                                   _("The node '%s' was not found."),
                                   svn_dirent_local_style(src_abspath,
                                                          scratch_pool));
        default:
          break;
      }

    SVN_ERR(svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL,
                                 &dst_repos_root_url, &dst_repos_uuid, NULL,
                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                                 NULL, NULL, NULL, NULL, NULL, NULL,
                                 NULL, NULL, NULL, NULL,
                                 NULL, NULL, NULL,
                                 db, dstdir_abspath,
                                 scratch_pool, scratch_pool));

    if (!src_repos_root_url)
      {
        if (src_status == svn_wc__db_status_added)
          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
                                           &src_repos_root_url,
                                           &src_repos_uuid, NULL, NULL, NULL,
                                           NULL,
                                           db, src_abspath,
                                           scratch_pool, scratch_pool));
        else
          /* If not added, the node must have a base or we can't copy */
          SVN_ERR(svn_wc__db_scan_base_repos(NULL, &src_repos_root_url,
                                             &src_repos_uuid,
                                             db, src_abspath,
                                             scratch_pool, scratch_pool));
      }

    if (!dst_repos_root_url)
      {
        if (dstdir_status == svn_wc__db_status_added)
          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
                                           &dst_repos_root_url,
                                           &dst_repos_uuid, NULL, NULL, NULL,
                                           NULL,
                                           db, dstdir_abspath,
                                           scratch_pool, scratch_pool));
        else
          /* If not added, the node must have a base or we can't copy */
          SVN_ERR(svn_wc__db_scan_base_repos(NULL, &dst_repos_root_url,
                                             &dst_repos_uuid,
                                             db, dstdir_abspath,
                                             scratch_pool, scratch_pool));
      }

    if (strcmp(src_repos_root_url, dst_repos_root_url) != 0
        || strcmp(src_repos_uuid, dst_repos_uuid) != 0)
      return svn_error_createf(
         SVN_ERR_WC_INVALID_SCHEDULE, NULL,
         _("Cannot copy to '%s', as it is not from repository '%s'; "
           "it is from '%s'"),
         svn_dirent_local_style(dst_abspath, scratch_pool),
         src_repos_root_url, dst_repos_root_url);

    if (dstdir_status == svn_wc__db_status_deleted)
      return svn_error_createf(
         SVN_ERR_WC_INVALID_SCHEDULE, NULL,
         _("Cannot copy to '%s' as it is scheduled for deletion"),
         svn_dirent_local_style(dst_abspath, scratch_pool));
         /* ### should report dstdir_abspath instead of dst_abspath? */
  }

  /* TODO(#2843): Rework the error report. */
  /* Check if the copy target is missing or hidden and thus not exist on the
     disk, before actually doing the file copy. */
  {
    svn_wc__db_status_t dst_status;
    svn_error_t *err;

    err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL,
                               db, dst_abspath, scratch_pool, scratch_pool);

    if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
      return svn_error_trace(err);

    svn_error_clear(err);

    if (!err)
      switch (dst_status)
        {
          case svn_wc__db_status_excluded:
            return svn_error_createf(
                     SVN_ERR_ENTRY_EXISTS, NULL,
                     _("'%s' is already under version control "
                       "but is excluded."),
                     svn_dirent_local_style(dst_abspath, scratch_pool));
          case svn_wc__db_status_server_excluded:
            return svn_error_createf(
                     SVN_ERR_ENTRY_EXISTS, NULL,
                     _("'%s' is already under version control"),
                     svn_dirent_local_style(dst_abspath, scratch_pool));

          case svn_wc__db_status_deleted:
          case svn_wc__db_status_not_present:
            break; /* OK to add */

          default:
            return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
                               _("There is already a versioned item '%s'"),
                               svn_dirent_local_style(dst_abspath,
                                                      scratch_pool));
        }
  }

  /* Check that the target path is not obstructed, if required. */
  if (!metadata_only)
    {
      svn_node_kind_t dst_kind;

      /* (We need only to check the root of the copy, not every path inside
         copy_versioned_file/_dir.) */
      SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
      if (dst_kind != svn_node_none)
        return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
                                 _("'%s' already exists and is in the way"),
                                 svn_dirent_local_style(dst_abspath,
                                                        scratch_pool));
    }

  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
                                         dst_abspath,
                                         scratch_pool, scratch_pool));

  if (src_db_kind == svn_wc__db_kind_file
      || src_db_kind == svn_wc__db_kind_symlink)
    {
      SVN_ERR(copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
                                  tmpdir_abspath, checksum,
                                  metadata_only, conflicted,
                                  cancel_func, cancel_baton,
                                  notify_func, notify_baton,
                                  scratch_pool));
    }
  else
    {
      SVN_ERR(copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
                                 tmpdir_abspath,
                                 metadata_only,
                                 cancel_func, cancel_baton,
                                 notify_func, notify_baton,
                                 scratch_pool));
    }

  return SVN_NO_ERROR;
}

/* Remove the conflict markers of NODE_ABSPATH, that were left over after
   copying NODE_ABSPATH from SRC_ABSPATH.

   Only use this function when you know what you're doing. This function
   explicitly ignores some case insensitivity issues!

   */
static svn_error_t *
remove_node_conflict_markers(svn_wc__db_t *db,
                             const char *src_abspath,
                             const char *node_abspath,
                             apr_pool_t *scratch_pool)
{
  const apr_array_header_t *conflicts;

  SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, src_abspath,
                                    scratch_pool, scratch_pool));

  /* Do we have conflict markers that should be removed? */
  if (conflicts != NULL)
    {
      int i;
      const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
      const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);

      /* No iterpool: Maximum number of possible conflict markers is 4 */

      for (i = 0; i < conflicts->nelts; i++)
        {
          const svn_wc_conflict_description2_t *desc;
          const char *child_relpath;
          const char *child_abpath;

          desc = APR_ARRAY_IDX(conflicts, i,
                               const svn_wc_conflict_description2_t*);

          if (desc->kind != svn_wc_conflict_kind_text
              && desc->kind != svn_wc_conflict_kind_property)
            continue;

          if (desc->base_abspath != NULL)
            {
              child_relpath = svn_dirent_is_child(src_dir, desc->base_abspath,
                                                  NULL);

              if (child_relpath)
                {
                  child_abpath = svn_dirent_join(dst_dir, child_relpath,
                                                 scratch_pool);

                  SVN_ERR(svn_io_remove_file2(child_abpath, TRUE,
                                              scratch_pool));
                }
            }
          if (desc->their_abspath != NULL)
            {
              child_relpath = svn_dirent_is_child(src_dir, desc->their_abspath,
                                                  NULL);

              if (child_relpath)
                {
                  child_abpath = svn_dirent_join(dst_dir, child_relpath,
                                                 scratch_pool);

                  SVN_ERR(svn_io_remove_file2(child_abpath, TRUE,
                                              scratch_pool));
                }
            }
          if (desc->my_abspath != NULL)
            {
              child_relpath = svn_dirent_is_child(src_dir, desc->my_abspath,
                                                  NULL);

              if (child_relpath)
                {
                  child_abpath = svn_dirent_join(dst_dir, child_relpath,
                                                 scratch_pool);

                  /* ### Copy child_abspath to node_abspath if it exists? */
                  SVN_ERR(svn_io_remove_file2(child_abpath, TRUE,
                                              scratch_pool));
                }
            }
        }
    }

  return SVN_NO_ERROR;
}

/* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
   after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.

   This function doesn't remove the conflict markers on WC_DIR_ABSPATH
   itself!

   Only use this function when you know what you're doing. This function
   explicitly ignores some case insensitivity issues!
   */
static svn_error_t *
remove_all_conflict_markers(svn_wc__db_t *db,
                            const char *src_dir_abspath,
                            const char *wc_dir_abspath,
                            apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  apr_hash_t *nodes;
  apr_hash_t *conflicts; /* Unused */
  apr_hash_index_t *hi;

  /* Reuse a status helper to obtain all subdirs and conflicts in a single
     db transaction. */
  /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
          artillery. */
  SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
                                        src_dir_abspath,
                                        scratch_pool, iterpool));

  for (hi = apr_hash_first(scratch_pool, nodes);
       hi;
       hi = apr_hash_next(hi))
    {
      const char *name = svn__apr_hash_index_key(hi);
      struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi);

      if (info->conflicted)
        {
          svn_pool_clear(iterpool);
          SVN_ERR(remove_node_conflict_markers(
                            db,
                            svn_dirent_join(src_dir_abspath, name, iterpool),
                            svn_dirent_join(wc_dir_abspath, name, iterpool),
                            iterpool));
        }
      if (info->kind == svn_wc__db_kind_dir)
        {
          svn_pool_clear(iterpool);
          SVN_ERR(remove_all_conflict_markers(
                            db,
                            svn_dirent_join(src_dir_abspath, name, iterpool),
                            svn_dirent_join(wc_dir_abspath, name, iterpool),
                            iterpool));
        }
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}

svn_error_t *
svn_wc_move(svn_wc_context_t *wc_ctx,
            const char *src_abspath,
            const char *dst_abspath,
            svn_boolean_t metadata_only,
            svn_cancel_func_t cancel_func,
            void *cancel_baton,
            svn_wc_notify_func2_t notify_func,
            void *notify_baton,
            apr_pool_t *scratch_pool)
{
  svn_wc__db_t *db = wc_ctx->db;
  SVN_ERR(svn_wc_copy3(wc_ctx, src_abspath, dst_abspath,
                       TRUE /* metadata_only */,
                       cancel_func, cancel_baton,
                       notify_func, notify_baton,
                       scratch_pool));

  /* Should we be using a workqueue for this move?  It's not clear.
     What should happen if the copy above is interrupted?  The user
     may want to abort the move and a workqueue might interfere with
     that. */
  if (!metadata_only)
    SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool));

  {
    svn_wc__db_kind_t kind;
    svn_boolean_t conflicted;

    SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                                 NULL, NULL, NULL, NULL, NULL, NULL,
                                 &conflicted, NULL, NULL, NULL,
                                 NULL, NULL, NULL,
                                 db, src_abspath,
                                 scratch_pool, scratch_pool));

    if (kind == svn_wc__db_kind_dir)
      SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
                                          scratch_pool));

    if (conflicted)
      SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath,
                                           scratch_pool));
  }

  SVN_ERR(svn_wc_delete4(wc_ctx, src_abspath, TRUE, FALSE,
                         cancel_func, cancel_baton,
                         notify_func, notify_baton,
                         scratch_pool));

  return SVN_NO_ERROR;
}