aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/update.c
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commit32547653cc5376642e1231fb644db99933ac8db4 (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_client/update.c
downloadsrc-32547653cc5376642e1231fb644db99933ac8db4.tar.gz
src-32547653cc5376642e1231fb644db99933ac8db4.zip
Import trimmed svn-1.8.0-rc3vendor/subversion/subversion-1.8.0-rc3
Notes
Notes: svn path=/vendor/subversion/dist/; revision=251881 svn path=/vendor/subversion/subversion-1.8.0-rc3/; revision=251882; tag=vendor/subversion/subversion-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_client/update.c')
-rw-r--r--subversion/libsvn_client/update.c707
1 files changed, 707 insertions, 0 deletions
diff --git a/subversion/libsvn_client/update.c b/subversion/libsvn_client/update.c
new file mode 100644
index 000000000000..21f33ec93dc7
--- /dev/null
+++ b/subversion/libsvn_client/update.c
@@ -0,0 +1,707 @@
+/*
+ * update.c: wrappers around wc update 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_hash.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+/* Implements svn_wc_dirents_func_t for update and switch handling. Assumes
+ a struct svn_client__dirent_fetcher_baton_t * baton */
+svn_error_t *
+svn_client__dirent_fetcher(void *baton,
+ apr_hash_t **dirents,
+ const char *repos_root_url,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_client__dirent_fetcher_baton_t *dfb = baton;
+ const char *old_url = NULL;
+ const char *session_relpath;
+ svn_node_kind_t kind;
+ const char *url;
+
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+
+ if (!svn_uri__is_ancestor(dfb->anchor_url, url))
+ {
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_url, dfb->ra_session,
+ url, scratch_pool));
+ session_relpath = "";
+ }
+ else
+ SVN_ERR(svn_ra_get_path_relative_to_session(dfb->ra_session,
+ &session_relpath, url,
+ scratch_pool));
+
+ /* Is session_relpath still a directory? */
+ SVN_ERR(svn_ra_check_path(dfb->ra_session, session_relpath,
+ dfb->target_revision, &kind, scratch_pool));
+
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_ra_get_dir2(dfb->ra_session, dirents, NULL, NULL,
+ session_relpath, dfb->target_revision,
+ SVN_DIRENT_KIND, result_pool));
+ else
+ *dirents = NULL;
+
+ if (old_url)
+ SVN_ERR(svn_ra_reparent(dfb->ra_session, old_url, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Code. ***/
+
+/* Set *CLEAN_CHECKOUT to FALSE only if LOCAL_ABSPATH is a non-empty
+ folder. ANCHOR_ABSPATH is the w/c root and LOCAL_ABSPATH will still
+ be considered empty, if it is equal to ANCHOR_ABSPATH and only
+ contains the admin sub-folder.
+ If the w/c folder already exists but cannot be openend, we return
+ "unclean" - just in case. Most likely, the caller will have to bail
+ out later due to the same error we got here.
+ */
+static svn_error_t *
+is_empty_wc(svn_boolean_t *clean_checkout,
+ const char *local_abspath,
+ const char *anchor_abspath,
+ apr_pool_t *pool)
+{
+ apr_dir_t *dir;
+ apr_finfo_t finfo;
+ svn_error_t *err;
+
+ /* "clean" until found dirty */
+ *clean_checkout = TRUE;
+
+ /* open directory. If it does not exist, yet, a clean one will
+ be created by the caller. */
+ err = svn_io_dir_open(&dir, local_abspath, pool);
+ if (err)
+ {
+ if (! APR_STATUS_IS_ENOENT(err->apr_err))
+ *clean_checkout = FALSE;
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ for (err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool);
+ err == SVN_NO_ERROR;
+ err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool))
+ {
+ /* Ignore entries for this dir and its parent, robustly.
+ (APR promises that they'll come first, so technically
+ this guard could be moved outside the loop. But Ryan Bloom
+ says he doesn't believe it, and I believe him. */
+ if (! (finfo.name[0] == '.'
+ && (finfo.name[1] == '\0'
+ || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
+ {
+ if ( ! svn_wc_is_adm_dir(finfo.name, pool)
+ || strcmp(local_abspath, anchor_abspath) != 0)
+ {
+ *clean_checkout = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (err)
+ {
+ if (! APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* There was some issue reading the folder content.
+ * We better disable optimizations in that case. */
+ *clean_checkout = FALSE;
+ }
+
+ svn_error_clear(err);
+ }
+
+ return svn_io_dir_close(dir);
+}
+
+/* A conflict callback that simply records the conflicted path in BATON.
+
+ Implements svn_wc_conflict_resolver_func2_t.
+*/
+static svn_error_t *
+record_conflict(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *description,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicted_paths = baton;
+
+ svn_hash_sets(conflicted_paths,
+ apr_pstrdup(apr_hash_pool_get(conflicted_paths),
+ description->local_abspath), "");
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* This is a helper for svn_client__update_internal(), which see for
+ an explanation of most of these parameters. Some stuff that's
+ unique is as follows:
+
+ ANCHOR_ABSPATH is the local absolute path of the update anchor.
+ This is typically either the same as LOCAL_ABSPATH, or the
+ immediate parent of LOCAL_ABSPATH.
+
+ If NOTIFY_SUMMARY is set (and there's a notification handler in
+ CTX), transmit the final update summary upon successful
+ completion of the update.
+
+ Add the paths of any conflict victims to CONFLICTED_PATHS, if that
+ is not null.
+*/
+static svn_error_t *
+update_internal(svn_revnum_t *result_rev,
+ apr_hash_t *conflicted_paths,
+ const char *local_abspath,
+ const char *anchor_abspath,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t *timestamp_sleep,
+ svn_boolean_t notify_summary,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *update_editor;
+ void *update_edit_baton;
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ const char *corrected_url;
+ const char *target;
+ const char *repos_root_url;
+ const char *repos_relpath;
+ const char *repos_uuid;
+ const char *anchor_url;
+ svn_revnum_t revnum;
+ svn_boolean_t use_commit_times;
+ svn_boolean_t clean_checkout = FALSE;
+ const char *diff3_cmd;
+ apr_hash_t *wcroot_iprops;
+ svn_opt_revision_t opt_rev;
+ svn_ra_session_t *ra_session;
+ const char *preserved_exts_str;
+ apr_array_header_t *preserved_exts;
+ struct svn_client__dirent_fetcher_baton_t dfb;
+ svn_boolean_t server_supports_depth;
+ svn_boolean_t cropping_target;
+ svn_boolean_t target_conflicted = FALSE;
+ svn_config_t *cfg = ctx->config
+ ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+
+ if (result_rev)
+ *result_rev = SVN_INVALID_REVNUM;
+
+ /* An unknown depth can't be sticky. */
+ if (depth == svn_depth_unknown)
+ depth_is_sticky = FALSE;
+
+ if (strcmp(local_abspath, anchor_abspath))
+ target = svn_dirent_basename(local_abspath, pool);
+ else
+ target = "";
+
+ /* Check if our anchor exists in BASE. If it doesn't we can't update. */
+ SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
+ &repos_uuid, NULL,
+ ctx->wc_ctx, anchor_abspath,
+ TRUE, FALSE,
+ pool, pool));
+
+ /* It does not make sense to update conflict victims. */
+ if (repos_relpath)
+ {
+ svn_error_t *err;
+ svn_boolean_t text_conflicted, prop_conflicted;
+
+ anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ pool);
+
+ err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
+ NULL,
+ ctx->wc_ctx, local_abspath, pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* tree-conflicts are handled by the update editor */
+ if (!err && (text_conflicted || prop_conflicted))
+ target_conflicted = TRUE;
+ }
+ else
+ anchor_url = NULL;
+
+ if (! anchor_url || target_conflicted)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *nt;
+
+ nt = svn_wc_create_notify(local_abspath,
+ target_conflicted
+ ? svn_wc_notify_skip_conflicted
+ : svn_wc_notify_update_skip_working_only,
+ pool);
+
+ ctx->notify_func2(ctx->notify_baton2, nt, pool);
+ }
+ return SVN_NO_ERROR;
+ }
+
+ /* We may need to crop the tree if the depth is sticky */
+ cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
+ if (cropping_target)
+ {
+ svn_node_kind_t target_kind;
+
+ if (depth == svn_depth_exclude)
+ {
+ SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
+ local_abspath,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+
+ /* Target excluded, we are done now */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
+ TRUE, TRUE, pool));
+ if (target_kind == svn_node_dir)
+ {
+ SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+ }
+ }
+
+ /* check whether the "clean c/o" optimization is applicable */
+ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool));
+
+ /* 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, pool));
+
+ /* 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));
+
+ /* 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, pool)
+ : NULL;
+
+ /* Let everyone know we're starting a real update (unless we're
+ asked not to). */
+ if (ctx->notify_func2 && notify_summary)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
+ 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;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* Open an RA session for the URL */
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ anchor_url,
+ anchor_abspath, NULL, TRUE,
+ TRUE, ctx, pool, pool));
+
+ /* If we got a corrected URL from the RA subsystem, we'll need to
+ relocate our working copy first. */
+ if (corrected_url)
+ {
+ const char *new_repos_root_url;
+
+ /* To relocate everything inside our repository we need the old and new
+ repos root. */
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool));
+
+ /* svn_client_relocate2() will check the uuid */
+ SVN_ERR(svn_client_relocate2(anchor_abspath, anchor_url,
+ new_repos_root_url, ignore_externals,
+ ctx, pool));
+
+ /* Store updated repository root for externals */
+ repos_root_url = new_repos_root_url;
+ /* ### We should update anchor_loc->repos_uuid too, although currently
+ * we don't use it. */
+ anchor_url = corrected_url;
+ }
+
+ /* Resolve unspecified REVISION now, because we need to retrieve the
+ correct inherited props prior to the editor drive and we need to
+ use the same value of HEAD for both. */
+ opt_rev.kind = revision->kind;
+ opt_rev.value = revision->value;
+ if (opt_rev.kind == svn_opt_revision_unspecified)
+ opt_rev.kind = svn_opt_revision_head;
+
+ /* ### todo: shouldn't svn_client__get_revision_number be able
+ to take a URL as easily as a local path? */
+ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
+ local_abspath, ra_session, &opt_rev,
+ pool));
+
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
+ dfb.ra_session = ra_session;
+ dfb.target_revision = revnum;
+ dfb.anchor_url = anchor_url;
+
+ SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
+ revnum, depth, ra_session,
+ ctx, pool, pool));
+
+ /* Fetch the update editor. If REVISION is invalid, that's okay;
+ the RA driver will call editor->set_target_revision later on. */
+ SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
+ &revnum, ctx->wc_ctx, anchor_abspath,
+ target, wcroot_iprops, use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
+ adds_as_modification,
+ server_supports_depth,
+ clean_checkout,
+ diff3_cmd, preserved_exts,
+ svn_client__dirent_fetcher, &dfb,
+ conflicted_paths ? record_conflict : NULL,
+ conflicted_paths,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool, 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_update3(ra_session, &reporter, &report_baton,
+ revnum, target,
+ (!server_supports_depth || depth_is_sticky
+ ? depth
+ : svn_depth_unknown),
+ FALSE /* send_copyfrom_args */,
+ FALSE /* ignore_ancestry */,
+ update_editor, update_edit_baton, pool, pool));
+
+ /* Past this point, we assume the WC is going to be modified so we will
+ * need to sleep for timestamps. */
+ *timestamp_sleep = TRUE;
+
+ /* Drive the reporter structure, describing the revisions within
+ PATH. When we call reporter->finish_report, the
+ update_editor will be driven by svn_repos_dir_delta2. */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
+ report_baton, TRUE,
+ depth, (! depth_is_sticky),
+ (! server_supports_depth),
+ use_commit_times,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+
+ /* We handle externals after the update is complete, so that
+ handling external items (and any errors therefrom) doesn't delay
+ the primary operation. */
+ if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
+ && (! ignore_externals))
+ {
+ apr_hash_t *new_externals;
+ apr_hash_t *new_depths;
+ SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
+ &new_depths,
+ ctx->wc_ctx, local_abspath,
+ depth, pool, pool));
+
+ SVN_ERR(svn_client__handle_externals(new_externals,
+ new_depths,
+ repos_root_url, local_abspath,
+ depth, timestamp_sleep,
+ ctx, pool));
+ }
+
+ /* Let everyone know we're finished here (unless we're asked not to). */
+ if (ctx->notify_func2 && notify_summary)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
+ 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, pool);
+ }
+
+ /* If the caller wants the result revision, give it to them. */
+ if (result_rev)
+ *result_rev = revnum;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__update_internal(svn_revnum_t *result_rev,
+ const char *local_abspath,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t make_parents,
+ svn_boolean_t innerupdate,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *anchor_abspath, *lockroot_abspath;
+ svn_error_t *err;
+ svn_opt_revision_t peg_revision = *revision;
+ apr_hash_t *conflicted_paths
+ = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(! (innerupdate && make_parents));
+
+ if (make_parents)
+ {
+ int i;
+ const char *parent_abspath = local_abspath;
+ apr_array_header_t *missing_parents =
+ apr_array_make(pool, 4, sizeof(const char *));
+
+ while (1)
+ {
+ /* Try to lock. If we can't lock because our target (or its
+ parent) isn't a working copy, we'll try to walk up the
+ tree to find a working copy, remembering this path's
+ parent as one we need to flesh out. */
+ err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
+ parent_abspath, !innerupdate,
+ pool, pool);
+ if (!err)
+ break;
+ if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
+ return err;
+ svn_error_clear(err);
+
+ /* Remember the parent of our update target as a missing
+ parent. */
+ parent_abspath = svn_dirent_dirname(parent_abspath, pool);
+ APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
+ }
+
+ /* Run 'svn up --depth=empty' (effectively) on the missing
+ parents, if any. */
+ anchor_abspath = lockroot_abspath;
+ for (i = missing_parents->nelts - 1; i >= 0; i--)
+ {
+ const char *missing_parent =
+ APR_ARRAY_IDX(missing_parents, i, const char *);
+
+ err = update_internal(result_rev, conflicted_paths,
+ missing_parent, anchor_abspath,
+ &peg_revision, svn_depth_empty, FALSE,
+ ignore_externals, allow_unver_obstructions,
+ adds_as_modification, timestamp_sleep,
+ FALSE, ctx, pool);
+ if (err)
+ goto cleanup;
+ anchor_abspath = missing_parent;
+
+ /* If we successfully updated a missing parent, let's re-use
+ the returned revision number for future updates for the
+ sake of consistency. */
+ peg_revision.kind = svn_opt_revision_number;
+ peg_revision.value.number = *result_rev;
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
+ local_abspath, !innerupdate,
+ pool, pool));
+ anchor_abspath = lockroot_abspath;
+ }
+
+ err = update_internal(result_rev, conflicted_paths,
+ local_abspath, anchor_abspath,
+ &peg_revision, depth, depth_is_sticky,
+ ignore_externals, allow_unver_obstructions,
+ adds_as_modification, timestamp_sleep,
+ TRUE, ctx, pool);
+
+ /* Give the conflict resolver callback the opportunity to
+ * resolve any conflicts that were raised. */
+ if (! err && ctx->conflict_func2)
+ {
+ err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
+ }
+
+ cleanup:
+ err = svn_error_compose_create(
+ err,
+ svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_client_update4(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t make_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *path = NULL;
+ svn_boolean_t sleep = FALSE;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ if (result_revs)
+ *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ }
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ svn_revnum_t result_rev;
+ const char *local_abspath;
+ path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ {
+ err = ctx->cancel_func(ctx->cancel_baton);
+ if (err)
+ goto cleanup;
+ }
+
+ err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
+ if (err)
+ goto cleanup;
+ err = svn_client__update_internal(&result_rev, local_abspath,
+ revision, depth, depth_is_sticky,
+ ignore_externals,
+ allow_unver_obstructions,
+ adds_as_modification,
+ make_parents,
+ FALSE, &sleep,
+ ctx,
+ iterpool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ goto cleanup;
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+
+ /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
+
+ result_rev = SVN_INVALID_REVNUM;
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(path,
+ svn_wc_notify_skip,
+ iterpool);
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
+ }
+ }
+ if (result_revs)
+ APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
+ }
+ svn_pool_destroy(iterpool);
+
+ cleanup:
+ if (sleep)
+ svn_io_sleep_for_timestamps((paths->nelts == 1) ? path : NULL, pool);
+
+ return svn_error_trace(err);
+}