summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/externals.c
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_client/externals.c
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_client/externals.c')
-rw-r--r--subversion/libsvn_client/externals.c1139
1 files changed, 1139 insertions, 0 deletions
diff --git a/subversion/libsvn_client/externals.c b/subversion/libsvn_client/externals.c
new file mode 100644
index 0000000..30748ea
--- /dev/null
+++ b/subversion/libsvn_client/externals.c
@@ -0,0 +1,1139 @@
+/*
+ * 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_rename(local_abspath, new_path, 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 svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ const char *defining_abspath,
+ svn_boolean_t *timestamp_sleep,
+ 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;
+
+ /* 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)
+ {
+ /* 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, local_abspath,
+ revision, svn_depth_unknown,
+ FALSE, FALSE, FALSE, TRUE,
+ FALSE, TRUE,
+ timestamp_sleep,
+ ctx, subpool));
+ 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, url, local_abspath, peg_revision,
+ revision, svn_depth_infinity,
+ FALSE, FALSE, timestamp_sleep,
+ 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 URL at REVISION using a
+ access baton that has a write lock. Use SCRATCH_POOL for temporary
+ allocations, and use the client context CTX. */
+static svn_error_t *
+switch_file_external(const char *local_abspath,
+ const char *url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *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'"),
+ 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 (kind == svn_node_file || 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_client__pathrev_t *switch_loc;
+ svn_revnum_t revnum;
+ apr_array_header_t *inherited_props;
+ const char *dir_abspath;
+ const char *target;
+
+ svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool);
+
+ /* Open an RA session to 'source' URL */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
+ url, dir_abspath,
+ peg_revision, revision,
+ ctx, 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(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,
+ url, peg_revision, 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, 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_boolean_t *timestamp_sleep,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ 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. */
+ 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_url,
+ &(new_item->peg_revision),
+ &(new_item->revision),
+ parent_dir_abspath,
+ timestamp_sleep, 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_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,
+ 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,
+ 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_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 = svn__apr_hash_index_key(hi);
+ const char *desc_text = svn__apr_hash_index_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,
+ svn__apr_hash_index_klen(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,
+ 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 = svn__apr_hash_index_key(hi);
+ const char *defining_abspath = svn__apr_hash_index_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,
+ TRUE, FALSE, iterpool));
+ if (kind == svn_node_none)
+ {
+ svn_error_t *err;
+
+ err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
+ if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ {
+ svn_error_clear(err);
+ break;
+ }
+ else
+ SVN_ERR(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 = svn__apr_hash_index_key(hi);
+ const char *desc_text = svn__apr_hash_index_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));
+
+ 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;
+}
+
OpenPOWER on IntegriCloud