blob: 7ff1ed25d80d076248ee49aef35787c5c0a00406 [file] [log] [blame]
/*
* externals.c: handle the svn:externals property
*
* ====================================================================
* 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 <apr_uri.h>
#include "svn_hash.h"
#include "svn_wc.h"
#include "svn_pools.h"
#include "svn_client.h"
#include "svn_types.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_props.h"
#include "svn_config.h"
#include "client.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
/* Remove the directory at LOCAL_ABSPATH from revision control, and do the
* same to any revision controlled directories underneath LOCAL_ABSPATH
* (including directories not referred to by parent svn administrative areas);
* then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a
* unique name in the same parent directory.
*
* Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
*
* Use SCRATCH_POOL for all temporary allocation.
*/
static svn_error_t *
relegate_dir_external(svn_wc_context_t *wc_ctx,
const char *wri_abspath,
const char *local_abspath,
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_error_t *err = SVN_NO_ERROR;
SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath,
FALSE, scratch_pool, scratch_pool));
err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE,
cancel_func, cancel_baton, scratch_pool);
if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
{
const char *parent_dir;
const char *dirname;
const char *new_path;
svn_error_clear(err);
err = SVN_NO_ERROR;
svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool);
/* Reserve the new dir name. */
SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
parent_dir, dirname, ".OLD",
svn_io_file_del_none,
scratch_pool, scratch_pool));
/* Sigh... We must fall ever so slightly from grace.
Ideally, there would be no window, however brief, when we
don't have a reservation on the new name. Unfortunately,
at least in the Unix (Linux?) version of apr_file_rename(),
you can't rename a directory over a file, because it's just
calling stdio rename(), which says:
ENOTDIR
A component used as a directory in oldpath or newpath
path is not, in fact, a directory. Or, oldpath is
a directory, and newpath exists but is not a directory
So instead, we get the name, then remove the file (ugh), then
rename the directory, hoping that nobody has gotten that name
in the meantime -- which would never happen in real life, so
no big deal.
*/
/* Do our best, but no biggy if it fails. The rename will fail. */
svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool));
/* Rename. If this is still a working copy we should use the working
copy rename function (to release open handles) */
err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path,
scratch_pool);
if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
{
svn_error_clear(err);
/* And if it is no longer a working copy, we should just rename
it */
err = svn_io_file_rename2(local_abspath, new_path, FALSE, scratch_pool);
}
/* ### TODO: We should notify the user about the rename */
if (notify_func)
{
svn_wc_notify_t *notify;
notify = svn_wc_create_notify(err ? local_abspath : new_path,
svn_wc_notify_left_local_modifications,
scratch_pool);
notify->kind = svn_node_dir;
notify->err = err;
notify_func(notify_baton, notify, scratch_pool);
}
}
return svn_error_trace(err);
}
/* Try to update a directory external at PATH to URL at REVISION.
Use POOL for temporary allocations, and use the client context CTX. */
static svn_error_t *
switch_dir_external(const char *local_abspath,
const char *url,
const char *url_from_externals_definition,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *revision,
const char *defining_abspath,
svn_boolean_t *timestamp_sleep,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_node_kind_t kind;
svn_error_t *err;
svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
svn_revnum_t external_rev = SVN_INVALID_REVNUM;
apr_pool_t *subpool = svn_pool_create(pool);
const char *repos_root_url;
const char *repos_uuid;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
if (peg_revision->kind == svn_opt_revision_number)
external_peg_rev = peg_revision->value.number;
if (revision->kind == svn_opt_revision_number)
external_rev = revision->value.number;
/*
* The code below assumes existing versioned paths are *not* part of
* the external's defining working copy.
* The working copy library does not support registering externals
* on top of existing BASE nodes and will error out if we try.
* So if the external target is part of the defining working copy's
* BASE tree, don't attempt to create the external. Doing so would
* leave behind a switched path instead of an external (since the
* switch succeeds but registration of the external in the DB fails).
* The working copy then cannot be updated until the path is switched back.
* See issue #4085.
*/
SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL,
&repos_root_url, &repos_uuid,
NULL, ctx->wc_ctx, local_abspath,
TRUE, /* ignore_enoent */
pool, pool));
if (kind != svn_node_unknown)
{
const char *wcroot_abspath;
const char *defining_wcroot_abspath;
SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
local_abspath, pool, pool));
SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx,
defining_abspath, pool, pool));
if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0)
return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
_("The external '%s' defined in %s at '%s' "
"cannot be checked out because '%s' is "
"already a versioned path."),
url_from_externals_definition,
SVN_PROP_EXTERNALS,
svn_dirent_local_style(defining_abspath,
pool),
svn_dirent_local_style(local_abspath,
pool));
}
/* If path is a directory, try to update/switch to the correct URL
and revision. */
SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
if (kind == svn_node_dir)
{
const char *node_url;
/* Doubles as an "is versioned" check. */
err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath,
pool, subpool);
if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
svn_error_clear(err);
err = SVN_NO_ERROR;
goto relegate;
}
else if (err)
return svn_error_trace(err);
if (node_url)
{
svn_boolean_t is_wcroot;
SVN_ERR(svn_wc__is_wcroot(&is_wcroot, ctx->wc_ctx, local_abspath,
pool));
if (! is_wcroot)
{
/* This can't be a directory external! */
err = svn_wc__external_remove(ctx->wc_ctx, defining_abspath,
local_abspath,
TRUE /* declaration_only */,
ctx->cancel_func, ctx->cancel_baton,
pool);
if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
/* New external... No problem that we can't remove it */
svn_error_clear(err);
err = NULL;
}
else if (err)
return svn_error_trace(err);
return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
_("The external '%s' defined in %s at '%s' "
"cannot be checked out because '%s' is "
"already a versioned path."),
url_from_externals_definition,
SVN_PROP_EXTERNALS,
svn_dirent_local_style(defining_abspath,
pool),
svn_dirent_local_style(local_abspath,
pool));
}
/* If we have what appears to be a version controlled
subdir, and its top-level URL matches that of our
externals definition, perform an update. */
if (strcmp(node_url, url) == 0)
{
SVN_ERR(svn_client__update_internal(NULL, timestamp_sleep,
local_abspath,
revision, svn_depth_unknown,
FALSE, FALSE, FALSE, TRUE,
FALSE, TRUE,
ra_session, ctx, subpool));
/* We just decided that this existing directory is an external,
so update the external registry with this information, like
when checking out an external */
SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
defining_abspath,
local_abspath, svn_node_dir,
repos_root_url, repos_uuid,
svn_uri_skip_ancestor(repos_root_url,
url, pool),
external_peg_rev,
external_rev,
pool));
svn_pool_destroy(subpool);
goto cleanup;
}
/* We'd really prefer not to have to do a brute-force
relegation -- blowing away the current external working
copy and checking it out anew -- so we'll first see if we
can get away with a generally cheaper relocation (if
required) and switch-style update.
To do so, we need to know the repository root URL of the
external working copy as it currently sits. */
err = svn_wc__node_get_repos_info(NULL, NULL,
&repos_root_url, &repos_uuid,
ctx->wc_ctx, local_abspath,
pool, subpool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
&& err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
return svn_error_trace(err);
svn_error_clear(err);
repos_root_url = NULL;
repos_uuid = NULL;
}
if (repos_root_url)
{
/* If the new external target URL is not obviously a
child of the external working copy's current
repository root URL... */
if (! svn_uri__is_ancestor(repos_root_url, url))
{
const char *repos_root;
/* ... then figure out precisely which repository
root URL that target URL *is* a child of ... */
SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
ctx, subpool, subpool));
/* ... and use that to try to relocate the external
working copy to the target location. */
err = svn_client_relocate2(local_abspath, repos_root_url,
repos_root, FALSE, ctx, subpool);
/* If the relocation failed because the new URL
points to a totally different repository, we've
no choice but to relegate and check out a new WC. */
if (err
&& (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
|| (err->apr_err
== SVN_ERR_CLIENT_INVALID_RELOCATION)))
{
svn_error_clear(err);
goto relegate;
}
else if (err)
return svn_error_trace(err);
/* If the relocation went without a hitch, we should
have a new repository root URL. */
repos_root_url = repos_root;
}
SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
peg_revision, revision,
svn_depth_infinity,
TRUE, FALSE, FALSE,
TRUE /* ignore_ancestry */,
timestamp_sleep,
ctx, subpool));
SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
defining_abspath,
local_abspath, svn_node_dir,
repos_root_url, repos_uuid,
svn_uri_skip_ancestor(
repos_root_url,
url, subpool),
external_peg_rev,
external_rev,
subpool));
svn_pool_destroy(subpool);
goto cleanup;
}
}
}
relegate:
/* Fall back on removing the WC and checking out a new one. */
/* Ensure that we don't have any RA sessions or WC locks from failed
operations above. */
svn_pool_destroy(subpool);
if (kind == svn_node_dir)
{
/* Buh-bye, old and busted ... */
SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
local_abspath,
ctx->cancel_func, ctx->cancel_baton,
ctx->notify_func2, ctx->notify_baton2,
pool));
}
else
{
/* The target dir might have multiple components. Guarantee
the path leading down to the last component. */
const char *parent = svn_dirent_dirname(local_abspath, pool);
SVN_ERR(svn_io_make_dir_recursively(parent, pool));
}
/* ... Hello, new hotness. */
SVN_ERR(svn_client__checkout_internal(NULL, timestamp_sleep,
url, local_abspath, peg_revision,
revision, svn_depth_infinity,
FALSE, FALSE,
ra_session,
ctx, pool));
SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
&repos_root_url,
&repos_uuid,
ctx->wc_ctx, local_abspath,
pool, pool));
SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
defining_abspath,
local_abspath, svn_node_dir,
repos_root_url, repos_uuid,
svn_uri_skip_ancestor(repos_root_url,
url, pool),
external_peg_rev,
external_rev,
pool));
cleanup:
/* Issues #4123 and #4130: We don't need to keep the newly checked
out external's DB open. */
SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
return SVN_NO_ERROR;
}
/* Try to update a file external at LOCAL_ABSPATH to SWITCH_LOC. This function
assumes caller has a write lock in CTX. Use SCRATCH_POOL for temporary
allocations, and use the client context CTX. */
static svn_error_t *
switch_file_external(const char *local_abspath,
const svn_client__pathrev_t *switch_loc,
const char *record_url,
const svn_opt_revision_t *record_peg_revision,
const svn_opt_revision_t *record_revision,
const char *def_dir_abspath,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
svn_config_t *cfg = ctx->config
? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
: NULL;
svn_boolean_t use_commit_times;
const char *diff3_cmd;
const char *preserved_exts_str;
const apr_array_header_t *preserved_exts;
svn_node_kind_t kind, external_kind;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
/* See if the user wants last-commit timestamps instead of current ones. */
SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
SVN_CONFIG_SECTION_MISCELLANY,
SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
/* Get the external diff3, if any. */
svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
if (diff3_cmd != NULL)
SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
/* See which files the user wants to preserve the extension of when
conflict files are made. */
svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
preserved_exts = *preserved_exts_str
? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
: NULL;
{
const char *wcroot_abspath;
SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
scratch_pool, scratch_pool));
/* File externals can only be installed inside the current working copy.
So verify if the working copy that contains/will contain the target
is the defining abspath, or one of its ancestors */
if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
return svn_error_createf(
SVN_ERR_WC_BAD_PATH, NULL,
_("Cannot insert a file external defined on '%s' "
"into the working copy '%s'."),
svn_dirent_local_style(def_dir_abspath,
scratch_pool),
svn_dirent_local_style(wcroot_abspath,
scratch_pool));
}
SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
TRUE, FALSE, scratch_pool));
SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
ctx->wc_ctx, local_abspath, local_abspath,
TRUE, scratch_pool, scratch_pool));
/* If there is a versioned item with this name, ensure it's a file
external before working with it. If there is no entry in the
working copy, then create an empty file and add it to the working
copy. */
if (kind != svn_node_none && kind != svn_node_unknown)
{
if (external_kind != svn_node_file)
{
return svn_error_createf(
SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
_("The file external from '%s' cannot overwrite the existing "
"versioned item at '%s'"),
switch_loc->url,
svn_dirent_local_style(local_abspath, scratch_pool));
}
}
else
{
svn_node_kind_t disk_kind;
SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
if (disk_kind == svn_node_file || disk_kind == svn_node_dir)
return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
_("The file external '%s' can not be "
"created because the node exists."),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
{
const svn_ra_reporter3_t *reporter;
void *report_baton;
const svn_delta_editor_t *switch_editor;
void *switch_baton;
svn_revnum_t revnum;
apr_array_header_t *inherited_props;
const char *target = svn_dirent_basename(local_abspath, scratch_pool);
/* Get the external file's iprops. */
SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
switch_loc->rev,
scratch_pool, scratch_pool));
SVN_ERR(svn_ra_reparent(ra_session,
svn_uri_dirname(switch_loc->url, scratch_pool),
scratch_pool));
SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
&revnum, ctx->wc_ctx,
local_abspath,
def_dir_abspath,
switch_loc->url,
switch_loc->repos_root_url,
switch_loc->repos_uuid,
inherited_props,
use_commit_times,
diff3_cmd, preserved_exts,
def_dir_abspath,
record_url,
record_peg_revision,
record_revision,
ctx->conflict_func2,
ctx->conflict_baton2,
ctx->cancel_func,
ctx->cancel_baton,
ctx->notify_func2,
ctx->notify_baton2,
scratch_pool, scratch_pool));
/* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
invalid revnum, that means RA will use the latest revision. */
SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
switch_loc->rev,
target, svn_depth_unknown, switch_loc->url,
FALSE /* send_copyfrom */,
TRUE /* ignore_ancestry */,
switch_editor, switch_baton,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
reporter, report_baton,
TRUE, use_commit_times,
ctx->cancel_func, ctx->cancel_baton,
ctx->notify_func2, ctx->notify_baton2,
scratch_pool));
if (ctx->notify_func2)
{
svn_wc_notify_t *notify
= svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
scratch_pool);
notify->kind = svn_node_none;
notify->content_state = notify->prop_state
= svn_wc_notify_state_inapplicable;
notify->lock_state = svn_wc_notify_lock_state_inapplicable;
notify->revision = revnum;
ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
}
}
return SVN_NO_ERROR;
}
/* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
directory externals */
static svn_error_t *
remove_external2(svn_boolean_t *removed,
svn_wc_context_t *wc_ctx,
const char *wri_abspath,
const char *local_abspath,
svn_node_kind_t external_kind,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
local_abspath,
(external_kind == svn_node_none),
cancel_func, cancel_baton,
scratch_pool));
*removed = TRUE;
return SVN_NO_ERROR;
}
static svn_error_t *
remove_external(svn_boolean_t *removed,
svn_wc_context_t *wc_ctx,
const char *wri_abspath,
const char *local_abspath,
svn_node_kind_t external_kind,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
*removed = FALSE;
switch (external_kind)
{
case svn_node_dir:
SVN_WC__CALL_WITH_WRITE_LOCK(
remove_external2(removed,
wc_ctx, wri_abspath,
local_abspath, external_kind,
cancel_func, cancel_baton,
scratch_pool),
wc_ctx, local_abspath, FALSE, scratch_pool);
break;
case svn_node_file:
default:
SVN_ERR(remove_external2(removed,
wc_ctx, wri_abspath,
local_abspath, external_kind,
cancel_func, cancel_baton,
scratch_pool));
break;
}
return SVN_NO_ERROR;
}
/* Called when an external that is in the EXTERNALS table is no longer
referenced from an svn:externals property */
static svn_error_t *
handle_external_item_removal(const svn_client_ctx_t *ctx,
const char *defining_abspath,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
svn_node_kind_t external_kind;
svn_node_kind_t kind;
svn_boolean_t removed = FALSE;
/* local_abspath should be a wcroot or a file external */
SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
ctx->wc_ctx, defining_abspath,
local_abspath, FALSE,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
scratch_pool));
if (external_kind != kind)
external_kind = svn_node_none; /* Only remove the registration */
err = remove_external(&removed,
ctx->wc_ctx, defining_abspath, local_abspath,
external_kind,
ctx->cancel_func, ctx->cancel_baton,
scratch_pool);
if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
{
svn_error_clear(err);
err = NULL; /* We removed the working copy, so we can't release the
lock that was stored inside */
}
if (ctx->notify_func2)
{
svn_wc_notify_t *notify =
svn_wc_create_notify(local_abspath,
svn_wc_notify_update_external_removed,
scratch_pool);
notify->kind = kind;
notify->err = err;
ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
{
notify = svn_wc_create_notify(local_abspath,
svn_wc_notify_left_local_modifications,
scratch_pool);
notify->kind = svn_node_dir;
notify->err = err;
ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
}
}
if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
{
svn_error_clear(err);
err = NULL;
}
return svn_error_trace(err);
}
static svn_error_t *
handle_external_item_change(svn_client_ctx_t *ctx,
const char *repos_root_url,
const char *parent_dir_abspath,
const char *parent_dir_url,
const char *local_abspath,
const char *old_defining_abspath,
const svn_wc_external_item2_t *new_item,
svn_ra_session_t *ra_session,
svn_boolean_t *timestamp_sleep,
apr_pool_t *scratch_pool)
{
svn_client__pathrev_t *new_loc;
const char *new_url;
svn_node_kind_t ext_kind;
SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
SVN_ERR_ASSERT(new_item != NULL);
/* Don't bother to check status, since we'll get that for free by
attempting to retrieve the hash values anyway. */
/* When creating the absolute URL, use the pool and not the
iterpool, since the hash table values outlive the iterpool and
any pointers they have should also outlive the iterpool. */
SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
new_item, repos_root_url,
parent_dir_url,
scratch_pool, scratch_pool));
/* Determine if the external is a file or directory. */
/* Get the RA connection, if needed. */
if (ra_session)
{
svn_error_t *err = svn_ra_reparent(ra_session, new_url, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
{
svn_error_clear(err);
ra_session = NULL;
}
else
return svn_error_trace(err);
}
else
{
SVN_ERR(svn_client__resolve_rev_and_url(&new_loc,
ra_session, new_url,
&(new_item->peg_revision),
&(new_item->revision), ctx,
scratch_pool));
SVN_ERR(svn_ra_reparent(ra_session, new_loc->url, scratch_pool));
}
}
if (!ra_session)
SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
new_url, NULL,
&(new_item->peg_revision),
&(new_item->revision), ctx,
scratch_pool));
SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
scratch_pool));
if (svn_node_none == ext_kind)
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
_("URL '%s' at revision %ld doesn't exist"),
new_loc->url, new_loc->rev);
if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
_("URL '%s' at revision %ld is not a file "
"or a directory"),
new_loc->url, new_loc->rev);
/* Not protecting against recursive externals. Detecting them in
the global case is hard, and it should be pretty obvious to a
user when it happens. Worst case: your disk fills up :-). */
/* First notify that we're about to handle an external. */
if (ctx->notify_func2)
{
ctx->notify_func2(
ctx->notify_baton2,
svn_wc_create_notify(local_abspath,
svn_wc_notify_update_external,
scratch_pool),
scratch_pool);
}
if (! old_defining_abspath)
{
/* The target dir might have multiple components. Guarantee the path
leading down to the last component. */
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
scratch_pool),
scratch_pool));
}
switch (ext_kind)
{
case svn_node_dir:
SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
new_item->url,
&(new_item->peg_revision),
&(new_item->revision),
parent_dir_abspath,
timestamp_sleep, ra_session, ctx,
scratch_pool));
break;
case svn_node_file:
if (strcmp(repos_root_url, new_loc->repos_root_url))
{
const char *local_repos_root_url;
const char *local_repos_uuid;
const char *ext_repos_relpath;
svn_error_t *err;
/*
* The working copy library currently requires that all files
* in the working copy have the same repository root URL.
* The URL from the file external's definition differs from the
* one used by the working copy. As a workaround, replace the
* root URL portion of the file external's URL, after making
* sure both URLs point to the same repository. See issue #4087.
*/
err = svn_wc__node_get_repos_info(NULL, NULL,
&local_repos_root_url,
&local_repos_uuid,
ctx->wc_ctx, parent_dir_abspath,
scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
&& err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
return svn_error_trace(err);
svn_error_clear(err);
local_repos_root_url = NULL;
local_repos_uuid = NULL;
}
ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
new_url, scratch_pool);
if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
ext_repos_relpath == NULL ||
strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Unsupported external: URL of file external '%s' "
"is not in repository '%s'"),
new_url, repos_root_url);
new_url = svn_path_url_add_component2(local_repos_root_url,
ext_repos_relpath,
scratch_pool);
SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
new_url,
NULL,
&(new_item->peg_revision),
&(new_item->revision),
ctx, scratch_pool));
}
SVN_ERR(switch_file_external(local_abspath,
new_loc,
new_url,
&new_item->peg_revision,
&new_item->revision,
parent_dir_abspath,
ra_session,
ctx,
scratch_pool));
break;
default:
SVN_ERR_MALFUNCTION();
break;
}
return SVN_NO_ERROR;
}
static svn_error_t *
wrap_external_error(const svn_client_ctx_t *ctx,
const char *target_abspath,
svn_error_t *err,
apr_pool_t *scratch_pool)
{
if (err && err->apr_err != SVN_ERR_CANCELLED)
{
if (ctx->notify_func2)
{
svn_wc_notify_t *notifier = svn_wc_create_notify(
target_abspath,
svn_wc_notify_failed_external,
scratch_pool);
notifier->err = err;
ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
}
svn_error_clear(err);
return SVN_NO_ERROR;
}
return err;
}
static svn_error_t *
handle_externals_change(svn_client_ctx_t *ctx,
const char *repos_root_url,
svn_boolean_t *timestamp_sleep,
const char *local_abspath,
const char *new_desc_text,
apr_hash_t *old_externals,
svn_depth_t ambient_depth,
svn_depth_t requested_depth,
svn_ra_session_t *ra_session,
apr_pool_t *scratch_pool)
{
apr_array_header_t *new_desc;
int i;
apr_pool_t *iterpool;
const char *url;
iterpool = svn_pool_create(scratch_pool);
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
/* Bag out if the depth here is too shallow for externals action. */
if ((requested_depth < svn_depth_infinity
&& requested_depth != svn_depth_unknown)
|| (ambient_depth < svn_depth_infinity
&& requested_depth < svn_depth_infinity))
return SVN_NO_ERROR;
if (new_desc_text)
SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
new_desc_text,
FALSE, scratch_pool));
else
new_desc = NULL;
SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
scratch_pool, iterpool));
SVN_ERR_ASSERT(url);
for (i = 0; new_desc && (i < new_desc->nelts); i++)
{
const char *old_defining_abspath;
svn_wc_external_item2_t *new_item;
const char *target_abspath;
svn_boolean_t under_root;
new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
svn_pool_clear(iterpool);
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
local_abspath, new_item->target_dir,
iterpool));
if (! under_root)
{
return svn_error_createf(
SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("Path '%s' is not in the working copy"),
svn_dirent_local_style(
svn_dirent_join(local_abspath, new_item->target_dir,
iterpool),
iterpool));
}
old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
SVN_ERR(wrap_external_error(
ctx, target_abspath,
handle_external_item_change(ctx,
repos_root_url,
local_abspath, url,
target_abspath,
old_defining_abspath,
new_item, ra_session,
timestamp_sleep,
iterpool),
iterpool));
/* And remove already processed items from the to-remove hash */
if (old_defining_abspath)
svn_hash_sets(old_externals, target_abspath, NULL);
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__handle_externals(apr_hash_t *externals_new,
apr_hash_t *ambient_depths,
const char *repos_root_url,
const char *target_abspath,
svn_depth_t requested_depth,
svn_boolean_t *timestamp_sleep,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
apr_hash_t *old_external_defs;
apr_hash_index_t *hi;
apr_pool_t *iterpool;
SVN_ERR_ASSERT(repos_root_url);
iterpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
ctx->wc_ctx, target_abspath,
scratch_pool, iterpool));
for (hi = apr_hash_first(scratch_pool, externals_new);
hi;
hi = apr_hash_next(hi))
{
const char *local_abspath = apr_hash_this_key(hi);
const char *desc_text = apr_hash_this_val(hi);
svn_depth_t ambient_depth = svn_depth_infinity;
svn_pool_clear(iterpool);
if (ambient_depths)
{
const char *ambient_depth_w;
ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
apr_hash_this_key_len(hi));
if (ambient_depth_w == NULL)
{
return svn_error_createf(
SVN_ERR_WC_CORRUPT, NULL,
_("Traversal of '%s' found no ambient depth"),
svn_dirent_local_style(local_abspath, scratch_pool));
}
else
{
ambient_depth = svn_depth_from_word(ambient_depth_w);
}
}
SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
local_abspath,
desc_text, old_external_defs,
ambient_depth, requested_depth,
ra_session, iterpool));
}
/* Remove the remaining externals */
for (hi = apr_hash_first(scratch_pool, old_external_defs);
hi;
hi = apr_hash_next(hi))
{
const char *item_abspath = apr_hash_this_key(hi);
const char *defining_abspath = apr_hash_this_val(hi);
const char *parent_abspath;
svn_pool_clear(iterpool);
SVN_ERR(wrap_external_error(
ctx, item_abspath,
handle_external_item_removal(ctx, defining_abspath,
item_abspath, iterpool),
iterpool));
/* Are there any unversioned directories between the removed
* external and the DEFINING_ABSPATH which we can remove? */
parent_abspath = item_abspath;
do {
svn_node_kind_t kind;
parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
FALSE /* show_deleted*/,
FALSE /* show_hidden */,
iterpool));
if (kind == svn_node_none)
{
svn_error_t *err;
err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
if (err)
{
if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
{
svn_error_clear(err);
break; /* No parents to delete */
}
else if (APR_STATUS_IS_ENOENT(err->apr_err)
|| APR_STATUS_IS_ENOTDIR(err->apr_err))
{
svn_error_clear(err);
/* Fall through; parent dir might be unversioned */
}
else
return svn_error_trace(err);
}
}
} while (strcmp(parent_abspath, defining_abspath) != 0);
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__export_externals(apr_hash_t *externals,
const char *from_url,
const char *to_abspath,
const char *repos_root_url,
svn_depth_t requested_depth,
const char *native_eol,
svn_boolean_t ignore_keywords,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
apr_hash_index_t *hi;
SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
for (hi = apr_hash_first(scratch_pool, externals);
hi;
hi = apr_hash_next(hi))
{
const char *local_abspath = apr_hash_this_key(hi);
const char *desc_text = apr_hash_this_val(hi);
const char *local_relpath;
const char *dir_url;
apr_array_header_t *items;
int i;
svn_pool_clear(iterpool);
SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
desc_text, FALSE,
iterpool));
if (! items->nelts)
continue;
local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
dir_url = svn_path_url_add_component2(from_url, local_relpath,
scratch_pool);
for (i = 0; i < items->nelts; i++)
{
const char *item_abspath;
const char *new_url;
svn_boolean_t under_root;
svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
svn_wc_external_item2_t *);
svn_pool_clear(sub_iterpool);
SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
local_abspath, item->target_dir,
sub_iterpool));
if (! under_root)
{
return svn_error_createf(
SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("Path '%s' is not in the working copy"),
svn_dirent_local_style(
svn_dirent_join(local_abspath, item->target_dir,
sub_iterpool),
sub_iterpool));
}
SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
repos_root_url,
dir_url, sub_iterpool,
sub_iterpool));
/* The target dir might have multiple components. Guarantee
the path leading down to the last component. */
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
sub_iterpool),
sub_iterpool));
/* First notify that we're about to handle an external. */
if (ctx->notify_func2)
{
ctx->notify_func2(
ctx->notify_baton2,
svn_wc_create_notify(item_abspath,
svn_wc_notify_update_external,
sub_iterpool),
sub_iterpool);
}
SVN_ERR(wrap_external_error(
ctx, item_abspath,
svn_client_export5(NULL, new_url, item_abspath,
&item->peg_revision,
&item->revision,
TRUE, FALSE, ignore_keywords,
svn_depth_infinity,
native_eol,
ctx, sub_iterpool),
sub_iterpool));
}
}
svn_pool_destroy(sub_iterpool);
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}