/* utils.c --- wc/client test utilities
 *
 * ====================================================================
 *    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.
 * ====================================================================
 */

#include "svn_error.h"
#include "svn_client.h"
#include "svn_cmdline.h"
#include "svn_pools.h"

#include "utils.h"

#include "../svn_test_fs.h"

#include "../../libsvn_wc/wc.h"
#include "../../libsvn_wc/wc-queries.h"
#define SVN_WC__I_AM_WC_DB
#include "../../libsvn_wc/wc_db_private.h"
#include "../../libsvn_wc/token-map.h"
svn_error_t *
svn_test__create_client_ctx(svn_client_ctx_t **ctx,
                            svn_test__sandbox_t *sbox,
                            apr_pool_t *result_pool)
{
  SVN_ERR(svn_client_create_context2(ctx, NULL, result_pool));

  SVN_ERR(svn_test__init_auth_baton(&(*ctx)->auth_baton,
                                    result_pool));

  if (sbox)
    (*ctx)->wc_ctx = sbox->wc_ctx;

  return SVN_NO_ERROR;
}

/* Create an empty repository and WC for the test TEST_NAME.  Set *REPOS_URL
 * to the URL of the new repository, *REPOS_DIR to its local path and
 * *WC_ABSPATH to the root path of the
 * new WC.
 *
 * Create the repository and WC in subdirectories called
 * REPOSITORIES_WORK_DIR/TEST_NAME and WCS_WORK_DIR/TEST_NAME respectively,
 * within the current working directory.
 *
 * Register the repo and WC to be cleaned up when the test suite exits. */
static svn_error_t *
create_repos_and_wc(const char **repos_url,
                    const char **repos_dir,
                    const char **wc_abspath,
                    const char *test_name,
                    const svn_test_opts_t *opts,
                    apr_pool_t *pool)
{
  const char *repos_path = svn_relpath_join(REPOSITORIES_WORK_DIR, test_name,
                                            pool);
  const char *wc_path = svn_relpath_join(WCS_WORK_DIR, test_name, pool);

  /* Remove the repo and WC dirs if they already exist, to ensure the test
   * will run even if a previous failed attempt was not cleaned up. */
  SVN_ERR(svn_io_remove_dir2(repos_path, TRUE, NULL, NULL, pool));
  SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));

  /* Create the parent dirs of the repo and WC if necessary. */
  SVN_ERR(svn_io_make_dir_recursively(REPOSITORIES_WORK_DIR, pool));
  SVN_ERR(svn_io_make_dir_recursively(WCS_WORK_DIR, pool));

  /* Create a repos. Register it for clean-up. Set *REPOS_URL to its path. */
  {
    /* Use a subpool to create the repository and then destroy the subpool
       so the repository's underlying filesystem is closed.  If opts->fs_type
       is BDB this prevents any attempt to open a second environment handle
       within the same process when we checkout the WC below.  BDB 4.4+ allows
       only a single environment handle to be open per process. */
    apr_pool_t *subpool = svn_pool_create(pool);

    SVN_ERR(svn_test__create_repos2(NULL, repos_url, repos_dir, repos_path,
                                    opts, pool, subpool));
    svn_pool_destroy(subpool);
  }

  /* Create a WC. Set *WC_ABSPATH to its path. */
  {
    apr_pool_t *subpool = svn_pool_create(pool); /* To cleanup CTX */
    svn_client_ctx_t *ctx;
    svn_opt_revision_t head_rev = { svn_opt_revision_head, {0} };

    SVN_ERR(svn_test__create_client_ctx(&ctx, NULL, subpool));
    SVN_ERR(svn_dirent_get_absolute(wc_abspath, wc_path, pool));
    SVN_ERR(svn_client_checkout3(NULL, *repos_url, *wc_abspath,
                                 &head_rev, &head_rev, svn_depth_infinity,
                                 FALSE /* ignore_externals */,
                                 FALSE /* allow_unver_obstructions */,
                                 ctx, subpool));
    svn_pool_destroy(subpool);
  }

  /* Register this WC for cleanup. */
  svn_test_add_dir_cleanup(*wc_abspath);

  return SVN_NO_ERROR;
}

WC_QUERIES_SQL_DECLARE_STATEMENTS(statements);

svn_error_t *
svn_test__create_fake_wc(const char *wc_abspath,
                         const char *extra_statements,
                         const svn_test__nodes_data_t nodes[],
                         const svn_test__actual_data_t actuals[],

                         apr_pool_t *scratch_pool)
{
  const char *dotsvn_abspath = svn_dirent_join(wc_abspath, ".svn",
                                               scratch_pool);
  svn_sqlite__db_t *sdb;
  const char **my_statements;
  int i;
  svn_sqlite__stmt_t *stmt;
  const apr_int64_t wc_id = 1;

  /* Allocate MY_STATEMENTS in RESULT_POOL because the SDB will continue to
   * refer to it over its lifetime. */
  my_statements = apr_palloc(scratch_pool, 7 * sizeof(const char *));
  my_statements[0] = statements[STMT_CREATE_SCHEMA];
  my_statements[1] = statements[STMT_INSTALL_SCHEMA_STATISTICS];
  my_statements[2] = extra_statements;
  my_statements[3] = NULL;

  /* Create fake-wc/SUBDIR/.svn/ for placing the metadata. */
  SVN_ERR(svn_io_make_dir_recursively(dotsvn_abspath, scratch_pool));
  SVN_ERR(svn_wc__db_util_open_db(&sdb, wc_abspath, "wc.db",
                                  svn_sqlite__mode_rwcreate,
                                  FALSE /* exclusive */, 0 /* timeout */,
                                  my_statements,
                                  scratch_pool, scratch_pool));
  for (i = 0; my_statements[i] != NULL; i++)
    SVN_ERR(svn_sqlite__exec_statements(sdb, /* my_statements[] */ i));

  SVN_ERR(svn_sqlite__close(sdb));

  if (!nodes && !actuals)
    return SVN_NO_ERROR;

  /* Re-open with normal set of statements */
  SVN_ERR(svn_wc__db_util_open_db(&sdb, wc_abspath, "wc.db",
                                  svn_sqlite__mode_readwrite,
                                  FALSE /* exclusive */, 0 /* timeout */,
                                  statements,
                                  scratch_pool, scratch_pool));

  if (nodes)
    {
      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
                                        STMT_INSERT_NODE));

      for (i = 0; nodes[i].local_relpath; i++)
        {
          SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnns",
                                wc_id,
                                nodes[i].local_relpath,
                                nodes[i].op_depth,
                                nodes[i].local_relpath[0]
                                  ? svn_relpath_dirname(nodes[i].local_relpath,
                                                        scratch_pool)
                                  : NULL,
                                nodes[i].presence));

          if (nodes[i].repos_relpath)
            {
                SVN_ERR(svn_sqlite__bind_int64(stmt, 5, nodes[i].repos_id));
                SVN_ERR(svn_sqlite__bind_text(stmt, 6, nodes[i].repos_relpath));
                SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, nodes[i].revision));
            }

          if (nodes[i].depth)
            SVN_ERR(svn_sqlite__bind_text(stmt, 9, nodes[i].depth));

          if (nodes[i].kind != 0)
            SVN_ERR(svn_sqlite__bind_token(stmt, 10, kind_map, nodes[i].kind));

          if (nodes[i].last_author || nodes[i].last_date)
            {
              SVN_ERR(svn_sqlite__bind_revnum(stmt, 11, nodes[i].last_revision));
              SVN_ERR(svn_sqlite__bind_int64(stmt, 12, nodes[i].last_date));
              SVN_ERR(svn_sqlite__bind_text(stmt, 13, nodes[i].last_author));
            }

          if (nodes[i].checksum)
            SVN_ERR(svn_sqlite__bind_text(stmt, 14, nodes[i].checksum));

          if (nodes[i].properties)
            SVN_ERR(svn_sqlite__bind_text(stmt, 15, nodes[i].properties));

          if (nodes[i].recorded_size || nodes[i].recorded_time)
            {
              SVN_ERR(svn_sqlite__bind_int64(stmt, 16, nodes[i].recorded_size));
              SVN_ERR(svn_sqlite__bind_int64(stmt, 17, nodes[i].recorded_time));
            }

          /* 18 is DAV cache */

          if (nodes[i].symlink_target)
            SVN_ERR(svn_sqlite__bind_text(stmt, 19, nodes[i].symlink_target));

          if (nodes[i].file_external)
            SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));

          if (nodes[i].moved_to)
            SVN_ERR(svn_sqlite__bind_text(stmt, 21, nodes[i].moved_to));

          if (nodes[i].moved_here)
            SVN_ERR(svn_sqlite__bind_int(stmt, 22, 1));

          if (nodes[i].inherited_props)
            SVN_ERR(svn_sqlite__bind_text(stmt, 23, nodes[i].inherited_props));

          SVN_ERR(svn_sqlite__step_done(stmt));
        }
    }

  if (actuals)
    {
      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
                                        STMT_INSERT_ACTUAL_NODE));

      for (i = 0; actuals[i].local_relpath; i++)
        {
          SVN_ERR(svn_sqlite__bindf(stmt, "isssss",
                                wc_id,
                                actuals[i].local_relpath,
                                actuals[i].local_relpath[0]
                                  ? svn_relpath_dirname(actuals[i].local_relpath,
                                                        scratch_pool)
                                  : NULL,
                                actuals[i].properties,
                                actuals[i].changelist,
                                actuals[i].conflict_data));

          SVN_ERR(svn_sqlite__step_done(stmt));
        }
    }

  SVN_ERR(svn_sqlite__close(sdb));

  return SVN_NO_ERROR;
}


svn_error_t *
svn_test__sandbox_create(svn_test__sandbox_t *sandbox,
                         const char *test_name,
                         const svn_test_opts_t *opts,
                         apr_pool_t *pool)
{
  sandbox->pool = pool;
  SVN_ERR(create_repos_and_wc(&sandbox->repos_url, &sandbox->repos_dir,
                              &sandbox->wc_abspath,
                              test_name, opts, pool));
  SVN_ERR(svn_wc_context_create(&sandbox->wc_ctx, NULL, pool, pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_file_write(svn_test__sandbox_t *b, const char *path, const char *text)
{
  apr_file_t *f;

  SVN_ERR(svn_io_file_open(&f, sbox_wc_path(b, path),
                           (APR_WRITE | APR_CREATE | APR_TRUNCATE),
                           APR_OS_DEFAULT,
                           b->pool));

  SVN_ERR(svn_io_file_write_full(f, text, strlen(text), NULL, b->pool));

  SVN_ERR(svn_io_file_close(f, b->pool));

  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_add(svn_test__sandbox_t *b, const char *path)
{
  const char *parent_abspath;

  path = sbox_wc_path(b, path);
  parent_abspath = svn_dirent_dirname(path, b->pool);
  SVN_ERR(svn_wc__acquire_write_lock(NULL, b->wc_ctx, parent_abspath, FALSE,
                                     b->pool, b->pool));
  SVN_ERR(svn_wc_add_from_disk3(b->wc_ctx, path, NULL /*props*/,
                                FALSE /* skip checks */,
                                NULL, NULL, b->pool));
  SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, parent_abspath, b->pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_disk_mkdir(svn_test__sandbox_t *b, const char *path)
{
  path = sbox_wc_path(b, path);
  SVN_ERR(svn_io_dir_make(path, APR_FPROT_OS_DEFAULT, b->pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_mkdir(svn_test__sandbox_t *b, const char *path)
{
  SVN_ERR(sbox_disk_mkdir(b, path));
  SVN_ERR(sbox_wc_add(b, path));
  return SVN_NO_ERROR;
}

#if 0 /* not used */
/* Copy the file or directory tree FROM_PATH to TO_PATH which must not exist
 * beforehand. */
svn_error_t *
sbox_disk_copy(svn_test__sandbox_t *b, const char *from_path, const char *to_path)
{
  const char *to_dir, *to_name;

  from_path = sbox_wc_path(b, from_path);
  to_path = sbox_wc_path(b, to_path);
  svn_dirent_split(&to_dir, &to_name, to_path, b->pool);
  return svn_io_copy_dir_recursively(from_path, to_dir, to_name,
                                     FALSE, NULL, NULL, b->pool);
}
#endif

svn_error_t *
sbox_wc_copy(svn_test__sandbox_t *b, const char *from_path, const char *to_path)
{
  const char *parent_abspath;

  from_path = sbox_wc_path(b, from_path);
  to_path = sbox_wc_path(b, to_path);
  parent_abspath = svn_dirent_dirname(to_path, b->pool);
  SVN_ERR(svn_wc__acquire_write_lock(NULL, b->wc_ctx, parent_abspath, FALSE,
                                     b->pool, b->pool));
  SVN_ERR(svn_wc_copy3(b->wc_ctx, from_path, to_path, FALSE,
                       NULL, NULL, NULL, NULL, b->pool));
  SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, parent_abspath, b->pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_copy_url(svn_test__sandbox_t *b, const char *from_url,
                 svn_revnum_t revision, const char *to_path)
{
  apr_pool_t *scratch_pool = b->pool;
  svn_client_ctx_t *ctx;
  svn_opt_revision_t rev = { svn_opt_revision_unspecified, {0} };
  svn_client_copy_source_t* src;
  apr_array_header_t *sources = apr_array_make(
                                        scratch_pool, 1,
                                        sizeof(svn_client_copy_source_t *));

  SVN_ERR(svn_test__create_client_ctx(&ctx, b, scratch_pool));

  if (SVN_IS_VALID_REVNUM(revision))
    {
      rev.kind = svn_opt_revision_number;
      rev.value.number = revision;
    }

  src = apr_pcalloc(scratch_pool, sizeof(*src));

  src->path = from_url;
  src->revision = &rev;
  src->peg_revision = &rev;

  APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = src;

  SVN_ERR(svn_client_copy7(sources, sbox_wc_path(b, to_path),
                           FALSE /* copy_as_child */,
                           FALSE /* make_parents */,
                           FALSE /* ignore_externals */,
                           FALSE /* metadata_only */,
                           FALSE, NULL /* pin_external */,
                           NULL /* revprops */,
                           NULL, NULL, /* commit_callback */
                           ctx, scratch_pool));

  ctx->wc_ctx = NULL;

  return SVN_NO_ERROR;
}


svn_error_t *
sbox_wc_revert(svn_test__sandbox_t *b, const char *path, svn_depth_t depth)
{
  const char *abspath = sbox_wc_path(b, path);
  const char *dir_abspath;
  const char *lock_root_abspath;

  if (strcmp(abspath, b->wc_abspath))
    dir_abspath = svn_dirent_dirname(abspath, b->pool);
  else
    dir_abspath = abspath;

  SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, b->wc_ctx,
                                     dir_abspath, FALSE /* lock_anchor */,
                                     b->pool, b->pool));
  SVN_ERR(svn_wc_revert6(b->wc_ctx, abspath, depth,
                         FALSE /* use_commit_times */,
                         NULL /* changelist_filter */,
                         FALSE /* clear_changelists */,
                         FALSE /* metadata_only */,
                         TRUE /*added_keep_local*/,
                         NULL, NULL, /* cancel baton + func */
                         NULL, NULL, /* notify baton + func */
                         b->pool));
  SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, lock_root_abspath, b->pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_delete(svn_test__sandbox_t *b, const char *path)
{
  const char *abspath = sbox_wc_path(b, path);
  const char *dir_abspath = svn_dirent_dirname(abspath, b->pool);
  const char *lock_root_abspath;

  SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, b->wc_ctx,
                                     dir_abspath, FALSE,
                                     b->pool, b->pool));
  SVN_ERR(svn_wc_delete4(b->wc_ctx, abspath, FALSE, TRUE,
                         NULL, NULL, /* cancel baton + func */
                         NULL, NULL, /* notify baton + func */
                         b->pool));
  SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, lock_root_abspath, b->pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_exclude(svn_test__sandbox_t *b, const char *path)
{
  const char *abspath = sbox_wc_path(b, path);
  const char *lock_root_abspath;

  SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, b->wc_ctx,
                                     abspath, TRUE,
                                     b->pool, b->pool));
  SVN_ERR(svn_wc_exclude(b->wc_ctx, abspath,
                         NULL, NULL, /* cancel baton + func */
                         NULL, NULL, /* notify baton + func */
                         b->pool));
  SVN_ERR(svn_wc__release_write_lock(b->wc_ctx, lock_root_abspath, b->pool));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_commit_ex(svn_test__sandbox_t *b,
                  apr_array_header_t *targets,
                  svn_depth_t depth)
{
  svn_client_ctx_t *ctx;
  apr_pool_t *scratch_pool = svn_pool_create(b->pool);
  svn_error_t *err;

  SVN_ERR(svn_test__create_client_ctx(&ctx, b, scratch_pool));

  /* A successfull commit doesn't close the ra session, but leaves that
     to the caller. This leaves the BDB handle open, which might cause
     problems in further test code. (op_depth_tests.c's repo_wc_copy) */
  err = svn_client_commit6(targets, depth,
                           FALSE /* keep_locks */,
                           FALSE /* keep_changelist */,
                           TRUE  /* commit_as_operations */,
                           TRUE  /* include_file_externals */,
                           FALSE /* include_dir_externals */,
                           NULL, NULL, NULL, NULL, ctx, scratch_pool);

  svn_pool_destroy(scratch_pool);

  return svn_error_trace(err);
}

svn_error_t *
sbox_wc_commit(svn_test__sandbox_t *b, const char *path)
{
  apr_array_header_t *targets = apr_array_make(b->pool, 1,
                                               sizeof(const char *));

  APR_ARRAY_PUSH(targets, const char *) = sbox_wc_path(b, path);
  return sbox_wc_commit_ex(b, targets, svn_depth_infinity);
}

svn_error_t *
sbox_wc_update_depth(svn_test__sandbox_t *b,
                     const char *path,
                     svn_revnum_t revnum,
                     svn_depth_t depth,
                     svn_boolean_t sticky)
{
  svn_client_ctx_t *ctx;
  apr_array_header_t *result_revs;
  apr_array_header_t *paths = apr_array_make(b->pool, 1,
                                             sizeof(const char *));
  svn_opt_revision_t revision;

  if (SVN_IS_VALID_REVNUM(revnum))
    {
      revision.kind = svn_opt_revision_number;
      revision.value.number = revnum;
    }
  else
    {
      revision.kind = svn_opt_revision_head;
    }

  APR_ARRAY_PUSH(paths, const char *) = sbox_wc_path(b, path);
  SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool));

  /* Note: Tree conflict resolver tests for libsvn_client depend on this
   * passing FALSE for adds_as_modifications so that tree conflicts are
   * created in case of add vs add upon update. */
  return svn_client_update4(&result_revs, paths, &revision, depth,
                            sticky, FALSE, FALSE, FALSE, FALSE,
                            ctx, b->pool);
}

svn_error_t *
sbox_wc_update(svn_test__sandbox_t *b, const char *path, svn_revnum_t revnum)
{
  SVN_ERR(sbox_wc_update_depth(b, path, revnum, svn_depth_unknown, FALSE));
  return SVN_NO_ERROR;
}

svn_error_t *
sbox_wc_switch(svn_test__sandbox_t *b,
               const char *path,
               const char *url,
               svn_depth_t depth)
{
  svn_client_ctx_t *ctx;
  svn_revnum_t result_rev;
  svn_opt_revision_t head_rev = { svn_opt_revision_head, {0} };

  url = apr_pstrcat(b->pool, b->repos_url, url, SVN_VA_NULL);
  SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool));

  return svn_client_switch3(&result_rev, sbox_wc_path(b, path), url,
                            &head_rev, &head_rev, depth,
                            FALSE /* depth_is_sticky */,
                            TRUE /* ignore_externals */,
                            FALSE /* allow_unver_obstructions */,
                            TRUE /* ignore_ancestry */,
                            ctx, b->pool);
}

svn_error_t *
sbox_wc_resolved(svn_test__sandbox_t *b, const char *path)
{
  return sbox_wc_resolve(b, path, svn_depth_infinity,
                         svn_wc_conflict_choose_merged);
}

svn_error_t *
sbox_wc_resolve(svn_test__sandbox_t *b, const char *path, svn_depth_t depth,
                svn_wc_conflict_choice_t conflict_choice)
{
  const char *lock_abspath;
  svn_error_t *err;

  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, b->wc_ctx,
                                                 sbox_wc_path(b, path),
                                                 b->pool, b->pool));
  err = svn_wc__resolve_conflicts(b->wc_ctx, sbox_wc_path(b, path),
                                  depth,
                                  TRUE /* resolve_text */,
                                  "" /* resolve_prop (ALL props) */,
                                  TRUE /* resolve_tree */,
                                  conflict_choice,
                                  NULL, NULL, /* conflict func */
                                  NULL, NULL, /* cancellation */
                                  NULL, NULL, /* notification */
                                  b->pool);

  err = svn_error_compose_create(err, svn_wc__release_write_lock(b->wc_ctx,
                                                                 lock_abspath,
                                                                 b->pool));
  return err;
}

svn_error_t *
sbox_wc_resolve_prop(svn_test__sandbox_t *b, const char *path,
                     const char *propname,
                     svn_wc_conflict_choice_t conflict_choice)
{
  const char *lock_abspath;
  svn_error_t *err;

  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, b->wc_ctx,
                                                 sbox_wc_path(b, path),
                                                 b->pool, b->pool));
  err = svn_wc__resolve_conflicts(b->wc_ctx, sbox_wc_path(b, path),
                                  svn_depth_empty,
                                  FALSE,
                                  propname,
                                  FALSE,
                                  conflict_choice,
                                  NULL, NULL, /* conflict func */
                                  NULL, NULL, /* cancellation */
                                  NULL, NULL, /* notification */
                                  b->pool);

  err = svn_error_compose_create(err, svn_wc__release_write_lock(b->wc_ctx,
                                                                 lock_abspath,
                                                                 b->pool));
  return err;
}


svn_error_t *
sbox_wc_move(svn_test__sandbox_t *b, const char *src, const char *dst)
{
  svn_client_ctx_t *ctx;
  apr_array_header_t *paths = apr_array_make(b->pool, 1,
                                             sizeof(const char *));

  SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool));

  APR_ARRAY_PUSH(paths, const char *) = sbox_wc_path(b, src);
  return svn_client_move7(paths, sbox_wc_path(b, dst),
                          FALSE /* move_as_child */,
                          FALSE /* make_parents */,
                          TRUE /* allow_mixed_revisions */,
                          FALSE /* metadata_only */,
                          NULL /* revprop_table */,
                          NULL, NULL, /* commit callback */
                          ctx, b->pool);
}

svn_error_t *
sbox_wc_propset(svn_test__sandbox_t *b,
           const char *name,
           const char *value,
           const char *path)
{
  svn_client_ctx_t *ctx;
  apr_array_header_t *paths = apr_array_make(b->pool, 1,
                                             sizeof(const char *));
  svn_string_t *pval = value ? svn_string_create(value, b->pool) : NULL;

  SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool));

  APR_ARRAY_PUSH(paths, const char *) = sbox_wc_path(b, path);
  return svn_client_propset_local(name, pval, paths, svn_depth_empty,
                                  TRUE /* skip_checks */,
                                  NULL, ctx, b->pool);
}

svn_error_t *
sbox_wc_relocate(svn_test__sandbox_t *b,
                 const char *new_repos_url)
{
  apr_pool_t *scratch_pool = b->pool;
  svn_client_ctx_t *ctx;

  SVN_ERR(svn_test__create_client_ctx(&ctx, b, scratch_pool));

  SVN_ERR(svn_client_relocate2(b->wc_abspath, b->repos_url,
                               new_repos_url, FALSE, ctx,scratch_pool));

  b->repos_url = apr_pstrdup(b->pool, new_repos_url);

  return SVN_NO_ERROR;
}

svn_error_t *
sbox_add_and_commit_greek_tree(svn_test__sandbox_t *b)
{
  const struct svn_test__tree_entry_t *node;

  for (node = svn_test__greek_tree_nodes; node->path; node++)
    {
      if (node->contents)
        {
          SVN_ERR(sbox_file_write(b, node->path, node->contents));
          SVN_ERR(sbox_wc_add(b, node->path));
        }
      else
        {
          SVN_ERR(sbox_wc_mkdir(b, node->path));
        }
    }

  SVN_ERR(sbox_wc_commit(b, ""));

  return SVN_NO_ERROR;
}
