/*
 * relocate.c:  wrapper around wc relocation 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 "svn_wc.h"
#include "svn_client.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "client.h"

#include "private/svn_wc_private.h"

#include "svn_private_config.h"


/*** Code. ***/

/* Repository root and UUID for a repository. */
struct url_uuid_t
{
  const char *root;
  const char *uuid;
};

struct validator_baton_t
{
  svn_client_ctx_t *ctx;
  const char *path;
  apr_array_header_t *url_uuids;
  apr_pool_t *pool;

};


static svn_error_t *
validator_func(void *baton,
               const char *uuid,
               const char *url,
               const char *root_url,
               apr_pool_t *pool)
{
  struct validator_baton_t *b = baton;
  struct url_uuid_t *url_uuid = NULL;
  const char *disable_checks;

  apr_array_header_t *uuids = b->url_uuids;
  int i;

  for (i = 0; i < uuids->nelts; ++i)
    {
      struct url_uuid_t *uu = &APR_ARRAY_IDX(uuids, i,
                                             struct url_uuid_t);
      if (svn_uri__is_ancestor(uu->root, url))
        {
          url_uuid = uu;
          break;
        }
    }

  disable_checks = getenv("SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION");
  if (disable_checks && (strcmp(disable_checks, "yes") == 0))
    {
      /* Lie about URL_UUID's components, claiming they match the
         expectations of the validation code below.  */
      url_uuid = apr_pcalloc(pool, sizeof(*url_uuid));
      url_uuid->root = apr_pstrdup(pool, root_url);
      url_uuid->uuid = apr_pstrdup(pool, uuid);
    }

  /* We use an RA session in a subpool to get the UUID of the
     repository at the new URL so we can force the RA session to close
     by destroying the subpool. */
  if (! url_uuid)
    {
      apr_pool_t *sesspool = svn_pool_create(pool);

      url_uuid = &APR_ARRAY_PUSH(uuids, struct url_uuid_t);
      SVN_ERR(svn_client_get_repos_root(&url_uuid->root,
                                        &url_uuid->uuid,
                                        url, b->ctx,
                                        pool, sesspool));

      svn_pool_destroy(sesspool);
    }

  /* Make sure the url is a repository root if desired. */
  if (root_url
      && strcmp(root_url, url_uuid->root) != 0)
    return svn_error_createf(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
                             _("'%s' is not the root of the repository"),
                             url);

  /* Make sure the UUIDs match. */
  if (uuid && strcmp(uuid, url_uuid->uuid) != 0)
    return svn_error_createf
      (SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
       _("The repository at '%s' has uuid '%s', but the WC has '%s'"),
       url, url_uuid->uuid, uuid);

  return SVN_NO_ERROR;
}

svn_error_t *
svn_client_relocate2(const char *wcroot_dir,
                     const char *from_prefix,
                     const char *to_prefix,
                     svn_boolean_t ignore_externals,
                     svn_client_ctx_t *ctx,
                     apr_pool_t *pool)
{
  struct validator_baton_t vb;
  const char *local_abspath;
  apr_hash_t *externals_hash = NULL;
  apr_hash_index_t *hi;
  apr_pool_t *iterpool = NULL;
  const char *old_repos_root_url, *new_repos_root_url;

  /* Populate our validator callback baton, and call the relocate code. */
  vb.ctx = ctx;
  vb.path = wcroot_dir;
  vb.url_uuids = apr_array_make(pool, 1, sizeof(struct url_uuid_t));
  vb.pool = pool;

  if (svn_path_is_url(wcroot_dir))
    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
                             _("'%s' is not a local path"),
                             wcroot_dir);

  SVN_ERR(svn_dirent_get_absolute(&local_abspath, wcroot_dir, pool));

  /* If we're ignoring externals, just relocate and get outta here. */
  if (ignore_externals)
    {
      return svn_error_trace(svn_wc_relocate4(ctx->wc_ctx, local_abspath,
                                              from_prefix, to_prefix,
                                              validator_func, &vb, pool));
    }

  /* Fetch our current root URL. */
  SVN_ERR(svn_client_get_repos_root(&old_repos_root_url, NULL /* uuid */,
                                    local_abspath, ctx, pool, pool));

  /* Perform the relocation. */
  SVN_ERR(svn_wc_relocate4(ctx->wc_ctx, local_abspath, from_prefix, to_prefix,
                           validator_func, &vb, pool));

  /* Now fetch new current root URL. */
  SVN_ERR(svn_client_get_repos_root(&new_repos_root_url, NULL /* uuid */,
                                    local_abspath, ctx, pool, pool));


  /* Relocate externals, too (if any). */
  SVN_ERR(svn_wc__externals_defined_below(&externals_hash,
                                          ctx->wc_ctx, local_abspath,
                                          pool, pool));
  if (! apr_hash_count(externals_hash))
    return SVN_NO_ERROR;

  iterpool = svn_pool_create(pool);

  for (hi = apr_hash_first(pool, externals_hash);
       hi != NULL;
       hi = apr_hash_next(hi))
    {
      svn_node_kind_t kind;
      const char *this_abspath = apr_hash_this_key(hi);

      svn_pool_clear(iterpool);

      SVN_ERR(svn_wc__read_external_info(&kind, NULL, NULL, NULL, NULL,
                                         ctx->wc_ctx,
                                         local_abspath, this_abspath,
                                         FALSE, iterpool, iterpool));

      if (kind == svn_node_dir)
        {
          const char *this_repos_root_url;
          svn_error_t *err;

          err = svn_client_get_repos_root(&this_repos_root_url, NULL /* uuid */,
                                          this_abspath, ctx, iterpool, iterpool);

          /* Ignore externals that aren't present in the working copy.
           * This can happen if an external is deleted from disk accidentally,
           * or if an external is configured on a locally added directory. */
          if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
            {
              svn_error_clear(err);
              continue;
            }
          SVN_ERR(err);

          if (strcmp(old_repos_root_url, this_repos_root_url) == 0)
            SVN_ERR(svn_client_relocate2(this_abspath, from_prefix, to_prefix,
                                         FALSE /* ignore_externals */,
                                         ctx, iterpool));
        }
    }

  svn_pool_destroy(iterpool);

  return SVN_NO_ERROR;
}
