The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * relocate.c: do wc repos relocation
 *
 * ====================================================================
 *    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_wc.h"
#include "svn_error.h"
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"

#include "wc.h"
#include "props.h"

#include "svn_private_config.h"


/* If the components of RELPATH exactly match (after being
   URI-encoded) the final components of URL, return a copy of URL
   minus those components allocated in RESULT_POOL; otherwise, return
   NULL. */
static const char *
url_remove_final_relpath(const char *url,
                         const char *relpath,
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
{
  char *result = apr_pstrdup(result_pool, url);
  char *result_end;
  const char *relpath_end;

  SVN_ERR_ASSERT_NO_RETURN(svn_path_is_url(url));
  SVN_ERR_ASSERT_NO_RETURN(svn_relpath_is_canonical(relpath));

  if (relpath[0] == 0)
    return result;

  relpath = svn_path_uri_encode(relpath, scratch_pool);
  result_end = result + strlen(result) - 1;
  relpath_end = relpath + strlen(relpath) - 1;

  while (relpath_end >= relpath)
    {
      if (*result_end != *relpath_end)
        return NULL;

      relpath_end--;
      result_end--;
    }

  if (*result_end != '/')
    return NULL;

  *result_end = 0;

  return result;
}

svn_error_t *
svn_wc_relocate4(svn_wc_context_t *wc_ctx,
                 const char *local_abspath,
                 const char *from,
                 const char *to,
                 svn_wc_relocation_validator3_t validator,
                 void *validator_baton,
                 apr_pool_t *scratch_pool)
{
  svn_wc__db_kind_t kind;
  const char *repos_relpath;
  const char *old_repos_root, *old_url;
  const char *new_repos_root, *new_url;
  size_t from_len;
  size_t old_url_len;
  const char *uuid;
  svn_boolean_t is_wc_root;

  SVN_ERR(svn_wc__strictly_is_wc_root(&is_wc_root, wc_ctx, local_abspath,
                                      scratch_pool));
  if (! is_wc_root)
    {
      const char *wcroot_abspath;
      svn_error_t *err;

      err = svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db,
                                  local_abspath, scratch_pool, scratch_pool);
      if (err)
        {
          svn_error_clear(err);
          return svn_error_createf(
            SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
            _("Cannot relocate '%s' as it is not the root of a working copy"),
            svn_dirent_local_style(local_abspath, scratch_pool));
        }
      else
        {
          return svn_error_createf(
            SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
            _("Cannot relocate '%s' as it is not the root of a working copy; "
              "try relocating '%s' instead"),
            svn_dirent_local_style(local_abspath, scratch_pool),
            svn_dirent_local_style(wcroot_abspath, scratch_pool));
        }
    }

  SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, &repos_relpath,
                               &old_repos_root, &uuid,
                               NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL,
                               wc_ctx->db, local_abspath, scratch_pool,
                               scratch_pool));

  if (kind != svn_wc__db_kind_dir)
    return svn_error_create(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
                            _("Cannot relocate a single file"));

  old_url = svn_path_url_add_component2(old_repos_root, repos_relpath,
                                        scratch_pool);
  old_url_len = strlen(old_url);
  from_len = strlen(from);
  if ((from_len > old_url_len) || (strncmp(old_url, from, strlen(from)) != 0))
    return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
                             _("Invalid source URL prefix: '%s' (does not "
                               "overlap target's URL '%s')"),
                             from, old_url);

  if (old_url_len == from_len)
    new_url = to;
  else
    new_url = apr_pstrcat(scratch_pool, to, old_url + from_len, (char *)NULL);
  if (! svn_path_is_url(new_url))
    return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
                             _("Invalid relocation destination: '%s' "
                               "(not a URL)"), new_url);

  new_repos_root = url_remove_final_relpath(new_url, repos_relpath,
                                            scratch_pool, scratch_pool);
  if (!new_repos_root)
    return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
                             _("Invalid relocation destination: '%s' "
                               "(does not point to target)" ), new_url);

  SVN_ERR(validator(validator_baton, uuid, new_url, new_repos_root,
                    scratch_pool));

  return svn_error_trace(svn_wc__db_global_relocate(wc_ctx->db, local_abspath,
                                                    new_repos_root,
                                                    scratch_pool));
}