summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/ra.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/ra.c')
-rw-r--r--subversion/libsvn_client/ra.c1147
1 files changed, 1147 insertions, 0 deletions
diff --git a/subversion/libsvn_client/ra.c b/subversion/libsvn_client/ra.c
new file mode 100644
index 0000000..33d3de5
--- /dev/null
+++ b/subversion/libsvn_client/ra.c
@@ -0,0 +1,1147 @@
+/*
+ * ra.c : routines for interacting with the RA layer
+ *
+ * ====================================================================
+ * 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 <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_sorts.h"
+#include "svn_ra.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "client.h"
+#include "mergeinfo.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_client_private.h"
+
+
+/* This is the baton that we pass svn_ra_open3(), and is associated with
+ the callback table we provide to RA. */
+typedef struct callback_baton_t
+{
+ /* Holds the directory that corresponds to the REPOS_URL at svn_ra_open3()
+ time. When callbacks specify a relative path, they are joined with
+ this base directory. */
+ const char *base_dir_abspath;
+
+ /* TEMPORARY: Is 'base_dir_abspath' a versioned path? cmpilato
+ suspects that the commit-to-multiple-disjoint-working-copies
+ code is getting this all wrong, sometimes passing an unversioned
+ (or versioned in a foreign wc) path here which sorta kinda
+ happens to work most of the time but is ultimately incorrect. */
+ svn_boolean_t base_dir_isversioned;
+
+ /* Used as wri_abspath for obtaining access to the pristine store */
+ const char *wcroot_abspath;
+
+ /* An array of svn_client_commit_item3_t * structures, present only
+ during working copy commits. */
+ const apr_array_header_t *commit_items;
+
+ /* A client context. */
+ svn_client_ctx_t *ctx;
+
+} callback_baton_t;
+
+
+
+static svn_error_t *
+open_tmp_file(apr_file_t **fp,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_open_unique_file3(fp, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+}
+
+
+/* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
+static svn_error_t *
+get_wc_prop(void *baton,
+ const char *relpath,
+ const char *name,
+ const svn_string_t **value,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ const char *local_abspath = NULL;
+ svn_error_t *err;
+
+ *value = NULL;
+
+ /* If we have a list of commit_items, search through that for a
+ match for this relative URL. */
+ if (cb->commit_items)
+ {
+ int i;
+ for (i = 0; i < cb->commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
+
+ if (! strcmp(relpath, item->session_relpath))
+ {
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
+ local_abspath = item->path;
+ break;
+ }
+ }
+
+ /* Commits can only query relpaths in the commit_items list
+ since the commit driver traverses paths as they are, or will
+ be, in the repository. Non-commits query relpaths in the
+ working copy. */
+ if (! local_abspath)
+ return SVN_NO_ERROR;
+ }
+
+ /* If we don't have a base directory, then there are no properties. */
+ else if (cb->base_dir_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ else
+ local_abspath = svn_dirent_join(cb->base_dir_abspath, relpath, pool);
+
+ err = svn_wc_prop_get2(value, cb->ctx->wc_ctx, local_abspath, name,
+ pool, pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ return svn_error_trace(err);
+}
+
+/* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
+static svn_error_t *
+push_wc_prop(void *baton,
+ const char *relpath,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ int i;
+
+ /* If we're committing, search through the commit_items list for a
+ match for this relative URL. */
+ if (! cb->commit_items)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Attempt to set wcprop '%s' on '%s' in a non-commit operation"),
+ name, svn_dirent_local_style(relpath, pool));
+
+ for (i = 0; i < cb->commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
+
+ if (strcmp(relpath, item->session_relpath) == 0)
+ {
+ apr_pool_t *changes_pool = item->incoming_prop_changes->pool;
+ svn_prop_t *prop = apr_palloc(changes_pool, sizeof(*prop));
+
+ prop->name = apr_pstrdup(changes_pool, name);
+ if (value)
+ prop->value = svn_string_dup(value, changes_pool);
+ else
+ prop->value = NULL;
+
+ /* Buffer the propchange to take effect during the
+ post-commit process. */
+ APR_ARRAY_PUSH(item->incoming_prop_changes, svn_prop_t *) = prop;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
+static svn_error_t *
+set_wc_prop(void *baton,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
+
+ /* We pass 1 for the 'force' parameter here. Since the property is
+ coming from the repository, we definitely want to accept it.
+ Ideally, we'd raise a conflict if, say, the received property is
+ svn:eol-style yet the file has a locally added svn:mime-type
+ claiming that it's binary. Probably the repository is still
+ right, but the conflict would remind the user to make sure.
+ Unfortunately, we don't have a clean mechanism for doing that
+ here, so we just set the property and hope for the best. */
+ return svn_error_trace(svn_wc_prop_set4(cb->ctx->wc_ctx, local_abspath,
+ name,
+ value, svn_depth_empty,
+ TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ pool));
+}
+
+
+/* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
+static svn_error_t *
+invalidate_wc_props(void *baton,
+ const char *path,
+ const char *prop_name,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
+
+ /* It's easier just to clear the whole dav_cache than to remove
+ individual items from it recursively like this. And since we
+ know that the RA providers that ship with Subversion only
+ invalidate the one property they use the most from this cache,
+ and that we're intentionally trying to get away from the use of
+ the cache altogether anyway, there's little to lose in wiping the
+ whole cache. Is it the most well-behaved approach to take? Not
+ so much. We choose not to care. */
+ return svn_error_trace(svn_wc__node_clear_dav_cache_recursive(
+ cb->ctx->wc_ctx, local_abspath, pool));
+}
+
+
+/* This implements the `svn_ra_get_wc_contents_func_t' interface. */
+static svn_error_t *
+get_wc_contents(void *baton,
+ svn_stream_t **contents,
+ const svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+
+ if (! cb->wcroot_abspath)
+ {
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(
+ svn_wc__get_pristine_contents_by_checksum(contents,
+ cb->ctx->wc_ctx,
+ cb->wcroot_abspath,
+ checksum,
+ pool, pool));
+}
+
+
+static svn_error_t *
+cancel_callback(void *baton)
+{
+ callback_baton_t *b = baton;
+ return svn_error_trace((b->ctx->cancel_func)(b->ctx->cancel_baton));
+}
+
+
+static svn_error_t *
+get_client_string(void *baton,
+ const char **name,
+ apr_pool_t *pool)
+{
+ callback_baton_t *b = baton;
+ *name = apr_pstrdup(pool, b->ctx->client_name);
+ return SVN_NO_ERROR;
+}
+
+
+#define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */
+
+svn_error_t *
+svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
+ const char **corrected_url,
+ const char *base_url,
+ const char *base_dir_abspath,
+ const apr_array_header_t *commit_items,
+ svn_boolean_t write_dav_props,
+ svn_boolean_t read_dav_props,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_callbacks2_t *cbtable;
+ callback_baton_t *cb = apr_pcalloc(result_pool, sizeof(*cb));
+ const char *uuid = NULL;
+
+ SVN_ERR_ASSERT(!write_dav_props || read_dav_props);
+ SVN_ERR_ASSERT(!read_dav_props || base_dir_abspath != NULL);
+ SVN_ERR_ASSERT(base_dir_abspath == NULL
+ || svn_dirent_is_absolute(base_dir_abspath));
+
+ SVN_ERR(svn_ra_create_callbacks(&cbtable, result_pool));
+ cbtable->open_tmp_file = open_tmp_file;
+ cbtable->get_wc_prop = read_dav_props ? get_wc_prop : NULL;
+ cbtable->set_wc_prop = (write_dav_props && read_dav_props)
+ ? set_wc_prop : NULL;
+ cbtable->push_wc_prop = commit_items ? push_wc_prop : NULL;
+ cbtable->invalidate_wc_props = (write_dav_props && read_dav_props)
+ ? invalidate_wc_props : NULL;
+ cbtable->auth_baton = ctx->auth_baton; /* new-style */
+ cbtable->progress_func = ctx->progress_func;
+ cbtable->progress_baton = ctx->progress_baton;
+ cbtable->cancel_func = ctx->cancel_func ? cancel_callback : NULL;
+ cbtable->get_client_string = get_client_string;
+ if (base_dir_abspath)
+ cbtable->get_wc_contents = get_wc_contents;
+
+ cb->commit_items = commit_items;
+ cb->ctx = ctx;
+
+ if (base_dir_abspath && (read_dav_props || write_dav_props))
+ {
+ svn_error_t *err = svn_wc__node_get_repos_info(NULL, NULL, NULL, &uuid,
+ ctx->wc_ctx,
+ base_dir_abspath,
+ result_pool,
+ scratch_pool);
+
+ if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
+ || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+ || err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
+ {
+ svn_error_clear(err);
+ uuid = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ cb->base_dir_isversioned = TRUE;
+ }
+ cb->base_dir_abspath = apr_pstrdup(result_pool, base_dir_abspath);
+ }
+
+ if (base_dir_abspath)
+ {
+ svn_error_t *err = svn_wc__get_wcroot(&cb->wcroot_abspath,
+ ctx->wc_ctx, base_dir_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY
+ && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ cb->wcroot_abspath = NULL;
+ }
+ }
+
+ /* If the caller allows for auto-following redirections, and the
+ RA->open() call above reveals a CORRECTED_URL, try the new URL.
+ We'll do this in a loop up to some maximum number follow-and-retry
+ attempts. */
+ if (corrected_url)
+ {
+ apr_hash_t *attempted = apr_hash_make(scratch_pool);
+ int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS;
+
+ *corrected_url = NULL;
+ while (attempts_left--)
+ {
+ const char *corrected = NULL;
+
+ /* Try to open the RA session. If this is our last attempt,
+ don't accept corrected URLs from the RA provider. */
+ SVN_ERR(svn_ra_open4(ra_session,
+ attempts_left == 0 ? NULL : &corrected,
+ base_url, uuid, cbtable, cb, ctx->config,
+ result_pool));
+
+ /* No error and no corrected URL? We're done here. */
+ if (! corrected)
+ break;
+
+ /* Notify the user that a redirect is being followed. */
+ if (ctx->notify_func2 != NULL)
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify_url(corrected,
+ svn_wc_notify_url_redirect,
+ scratch_pool);
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ /* Our caller will want to know what our final corrected URL was. */
+ *corrected_url = corrected;
+
+ /* Make sure we've not attempted this URL before. */
+ if (svn_hash_gets(attempted, corrected))
+ return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL,
+ _("Redirect cycle detected for URL '%s'"),
+ corrected);
+
+ /* Remember this CORRECTED_URL so we don't wind up in a loop. */
+ svn_hash_sets(attempted, corrected, (void *)1);
+ base_url = corrected;
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_ra_open4(ra_session, NULL, base_url,
+ uuid, cbtable, cb, ctx->config, result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+#undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
+
+
+svn_error_t *
+svn_client_open_ra_session2(svn_ra_session_t **session,
+ const char *url,
+ const char *wri_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_client__open_ra_session_internal(session, NULL, url,
+ wri_abspath, NULL,
+ FALSE, FALSE,
+ ctx, result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p,
+ svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t peg_rev = *peg_revision;
+ svn_opt_revision_t start_rev = *revision;
+ const char *url;
+ svn_revnum_t rev;
+
+ /* Default revisions: peg -> working or head; operative -> peg. */
+ SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev,
+ svn_path_is_url(path_or_url),
+ TRUE /* notice_local_mods */,
+ pool));
+
+ /* Run the history function to get the object's (possibly
+ different) url in REVISION. */
+ SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL,
+ ra_session, path_or_url, &peg_rev,
+ &start_rev, NULL, ctx, pool));
+
+ SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p,
+ ra_session, rev, url, pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
+ svn_client__pathrev_t **resolved_loc_p,
+ const char *path_or_url,
+ const char *base_dir_abspath,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ const char *initial_url;
+ const char *corrected_url;
+ svn_client__pathrev_t *resolved_loc;
+ const char *wri_abspath;
+
+ SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
+ pool));
+ if (! initial_url)
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("'%s' has no URL"), path_or_url);
+
+ if (base_dir_abspath)
+ wri_abspath = base_dir_abspath;
+ else if (!svn_path_is_url(path_or_url))
+ SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool));
+ else
+ wri_abspath = NULL;
+
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ initial_url,
+ wri_abspath,
+ NULL /* commit_items */,
+ base_dir_abspath != NULL,
+ base_dir_abspath != NULL,
+ ctx, pool, pool));
+
+ /* If we got a CORRECTED_URL, we'll want to refer to that as the
+ URL-ized form of PATH_OR_URL from now on. */
+ if (corrected_url && svn_path_is_url(path_or_url))
+ path_or_url = corrected_url;
+
+ SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session,
+ path_or_url, peg_revision, revision,
+ ctx, pool));
+
+ /* Make the session point to the real URL. */
+ SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool));
+
+ *ra_session_p = ra_session;
+ if (resolved_loc_p)
+ *resolved_loc_p = resolved_loc;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__ensure_ra_session_url(const char **old_session_url,
+ svn_ra_session_t *ra_session,
+ const char *session_url,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
+ if (! session_url)
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool));
+ if (strcmp(*old_session_url, session_url) != 0)
+ SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Repository Locations ***/
+
+struct gls_receiver_baton_t
+{
+ apr_array_header_t *segments;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+gls_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct gls_receiver_baton_t *b = baton;
+ APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) =
+ svn_location_segment_dup(segment, b->pool);
+ if (b->ctx->cancel_func)
+ SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton));
+ return SVN_NO_ERROR;
+}
+
+/* A qsort-compatible function which sorts svn_location_segment_t's
+ based on their revision range covering, resulting in ascending
+ (oldest-to-youngest) ordering. */
+static int
+compare_segments(const void *a, const void *b)
+{
+ const svn_location_segment_t *a_seg
+ = *((const svn_location_segment_t * const *) a);
+ const svn_location_segment_t *b_seg
+ = *((const svn_location_segment_t * const *) b);
+ if (a_seg->range_start == b_seg->range_start)
+ return 0;
+ return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
+}
+
+svn_error_t *
+svn_client__repos_location_segments(apr_array_header_t **segments,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct gls_receiver_baton_t gls_receiver_baton;
+ const char *old_session_url;
+ svn_error_t *err;
+
+ *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *));
+ gls_receiver_baton.segments = *segments;
+ gls_receiver_baton.ctx = ctx;
+ gls_receiver_baton.pool = pool;
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+ url, pool));
+ err = svn_ra_get_location_segments(ra_session, "", peg_revision,
+ start_revision, end_revision,
+ gls_receiver, &gls_receiver_baton,
+ pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_session_url, pool)));
+ qsort((*segments)->elts, (*segments)->nelts,
+ (*segments)->elt_size, compare_segments);
+ return SVN_NO_ERROR;
+}
+
+/* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
+ * had in revisions START_REVNUM and END_REVNUM. Return an error if the
+ * node cannot be traced back to one of the requested revisions.
+ *
+ * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and
+ * END_REVNUM must be valid revision numbers except that END_REVNUM may
+ * be SVN_INVALID_REVNUM if END_URL is NULL.
+ *
+ * RA_SESSION is an open RA session parented at URL.
+ */
+static svn_error_t *
+repos_locations(const char **start_url,
+ const char **end_url,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t peg_revnum,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_url, *start_path, *end_path;
+ apr_array_header_t *revs;
+ apr_hash_t *rev_locs;
+
+ SVN_ERR_ASSERT(peg_revnum != SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(start_revnum != SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(end_revnum != SVN_INVALID_REVNUM || end_url == NULL);
+
+ /* Avoid a network request in the common easy case. */
+ if (start_revnum == peg_revnum
+ && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM))
+ {
+ if (start_url)
+ *start_url = apr_pstrdup(result_pool, url);
+ if (end_url)
+ *end_url = apr_pstrdup(result_pool, url);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
+
+ revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t));
+ APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
+ if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM)
+ APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
+
+ SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
+ revs, scratch_pool));
+
+ /* We'd better have all the paths we were looking for! */
+ if (start_url)
+ {
+ start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
+ if (! start_path)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("Unable to find repository location for '%s' in revision %ld"),
+ url, start_revnum);
+ *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
+ result_pool);
+ }
+
+ if (end_url)
+ {
+ end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
+ if (! end_path)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("The location for '%s' for revision %ld does not exist in the "
+ "repository or refers to an unrelated object"),
+ url, end_revnum);
+
+ *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
+ result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_location(svn_client__pathrev_t **op_loc_p,
+ svn_ra_session_t *ra_session,
+ const svn_client__pathrev_t *peg_loc,
+ svn_revnum_t op_revnum,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *old_session_url;
+ const char *op_url;
+ svn_error_t *err;
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+ peg_loc->url, scratch_pool));
+ err = repos_locations(&op_url, NULL, ra_session,
+ peg_loc->url, peg_loc->rev,
+ op_revnum, SVN_INVALID_REVNUM,
+ result_pool, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)));
+
+ *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url,
+ peg_loc->repos_uuid,
+ op_revnum, op_url, result_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_locations(const char **start_url,
+ svn_revnum_t *start_revision,
+ const char **end_url,
+ svn_revnum_t *end_revision,
+ svn_ra_session_t *ra_session,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *url;
+ const char *local_abspath_or_url;
+ svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
+ svn_revnum_t start_revnum, end_revnum;
+ svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Ensure that we are given some real revision data to work with.
+ (It's okay if the END is unspecified -- in that case, we'll just
+ set it to the same thing as START.) */
+ if (revision->kind == svn_opt_revision_unspecified
+ || start->kind == svn_opt_revision_unspecified)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+
+ if (end == NULL)
+ {
+ static const svn_opt_revision_t unspecified_rev
+ = { svn_opt_revision_unspecified, { 0 } };
+
+ end = &unspecified_rev;
+ }
+
+ /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM.
+ If we are looking at the working version of a WC path that is scheduled
+ as a copy, then we need to use the copy-from URL and peg revision. */
+ if (! svn_path_is_url(path))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool));
+
+ if (revision->kind == svn_opt_revision_working)
+ {
+ const char *repos_root_url;
+ const char *repos_relpath;
+ svn_boolean_t is_copy;
+
+ SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath,
+ &repos_root_url, NULL, NULL,
+ ctx->wc_ctx, local_abspath_or_url,
+ FALSE, subpool, subpool));
+
+ if (repos_relpath)
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ pool);
+ else
+ url = NULL;
+
+ if (url && is_copy && ra_session)
+ {
+ const char *session_url;
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
+ subpool));
+
+ if (strcmp(session_url, url) != 0)
+ {
+ /* We can't use the caller provided RA session now :( */
+ ra_session = NULL;
+ }
+ }
+ }
+ else
+ url = NULL;
+
+ if (! url)
+ SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
+ local_abspath_or_url, pool, subpool));
+
+ if (!url)
+ {
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("'%s' has no URL"),
+ svn_dirent_local_style(path, pool));
+ }
+ }
+ else
+ {
+ local_abspath_or_url = path;
+ url = path;
+ }
+
+ /* ### We should be smarter here. If the callers just asks for BASE and
+ WORKING revisions, we should already have the correct URLs, so we
+ don't need to do anything more here in that case. */
+
+ /* Open a RA session to this URL if we don't have one already. */
+ if (! ra_session)
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
+ ctx, subpool, subpool));
+
+ /* Resolve the opt_revision_ts. */
+ if (peg_revnum == SVN_INVALID_REVNUM)
+ SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev,
+ ctx->wc_ctx, local_abspath_or_url,
+ ra_session, revision, pool));
+
+ SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
+ ctx->wc_ctx, local_abspath_or_url,
+ ra_session, start, pool));
+ if (end->kind == svn_opt_revision_unspecified)
+ end_revnum = start_revnum;
+ else
+ SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
+ ctx->wc_ctx, local_abspath_or_url,
+ ra_session, end, pool));
+
+ /* Set the output revision variables. */
+ if (start_revision)
+ {
+ *start_revision = start_revnum;
+ }
+ if (end_revision && end->kind != svn_opt_revision_unspecified)
+ {
+ *end_revision = end_revnum;
+ }
+
+ SVN_ERR(repos_locations(start_url, end_url,
+ ra_session, url, peg_revnum,
+ start_revnum, end_revnum,
+ pool, subpool));
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
+ const svn_client__pathrev_t *loc1,
+ const svn_client__pathrev_t *loc2,
+ svn_ra_session_t *session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *sesspool = NULL;
+ apr_hash_t *history1, *history2;
+ apr_hash_index_t *hi;
+ svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
+ const char *yc_relpath = NULL;
+ svn_boolean_t has_rev_zero_history1;
+ svn_boolean_t has_rev_zero_history2;
+
+ if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
+ {
+ *ancestor_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Open an RA session for the two locations. */
+ if (session == NULL)
+ {
+ sesspool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx,
+ sesspool, sesspool));
+ }
+
+ /* We're going to cheat and use history-as-mergeinfo because it
+ saves us a bunch of annoying custom data comparisons and such. */
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&history1,
+ &has_rev_zero_history1,
+ loc1,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ session, ctx, scratch_pool));
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
+ &has_rev_zero_history2,
+ loc2,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ session, ctx, scratch_pool));
+ /* Close the ra session if we opened one. */
+ if (sesspool)
+ svn_pool_destroy(sesspool);
+
+ /* Loop through the first location's history, check for overlapping
+ paths and ranges in the second location's history, and
+ remembering the youngest matching location. */
+ for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ apr_ssize_t path_len = svn__apr_hash_index_klen(hi);
+ svn_rangelist_t *ranges1 = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *ranges2, *common;
+
+ ranges2 = apr_hash_get(history2, path, path_len);
+ if (ranges2)
+ {
+ /* We have a path match. Now, did our two histories share
+ any revisions at that path? */
+ SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2,
+ TRUE, scratch_pool));
+ if (common->nelts)
+ {
+ svn_merge_range_t *yc_range =
+ APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *);
+ if ((! SVN_IS_VALID_REVNUM(yc_revision))
+ || (yc_range->end > yc_revision))
+ {
+ yc_revision = yc_range->end;
+ yc_relpath = path + 1;
+ }
+ }
+ }
+ }
+
+ /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
+ history is revision 0. */
+ if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2)
+ {
+ yc_relpath = "";
+ yc_revision = 0;
+ }
+
+ if (yc_relpath)
+ {
+ *ancestor_p = svn_client__pathrev_create_with_relpath(
+ loc1->repos_root_url, loc1->repos_uuid,
+ yc_revision, yc_relpath, result_pool);
+ }
+ else
+ {
+ *ancestor_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__youngest_common_ancestor(const char **ancestor_url,
+ svn_revnum_t *ancestor_rev,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *sesspool = svn_pool_create(scratch_pool);
+ svn_ra_session_t *session;
+ svn_client__pathrev_t *loc1, *loc2, *ancestor;
+
+ /* Resolve the two locations */
+ SVN_ERR(svn_client__ra_session_from_path2(&session, &loc1,
+ path_or_url1, NULL,
+ revision1, revision1,
+ ctx, sesspool));
+ SVN_ERR(svn_client__resolve_rev_and_url(&loc2, session,
+ path_or_url2, revision2, revision2,
+ ctx, scratch_pool));
+
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &ancestor, loc1, loc2, session, ctx, result_pool, scratch_pool));
+
+ if (ancestor)
+ {
+ *ancestor_url = ancestor->url;
+ *ancestor_rev = ancestor->rev;
+ }
+ else
+ {
+ *ancestor_url = NULL;
+ *ancestor_rev = SVN_INVALID_REVNUM;
+ }
+ svn_pool_destroy(sesspool);
+ return SVN_NO_ERROR;
+}
+
+
+struct ra_ev2_baton {
+ /* The working copy context, from the client context. */
+ svn_wc_context_t *wc_ctx;
+
+ /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
+ that repository node. */
+ apr_hash_t *relpath_map;
+};
+
+
+svn_error_t *
+svn_client__ra_provide_base(svn_stream_t **contents,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct ra_ev2_baton *reb = baton;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
+ if (!local_abspath)
+ {
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (*contents != NULL)
+ {
+ /* The pristine contents refer to the BASE, or to the pristine of
+ a copy/move to this location. Fetch the correct revision. */
+ SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
+ reb->wc_ctx, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__ra_provide_props(apr_hash_t **props,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct ra_ev2_baton *reb = baton;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
+ if (!local_abspath)
+ {
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (*props != NULL)
+ {
+ /* The pristine props refer to the BASE, or to the pristine props of
+ a copy/move to this location. Fetch the correct revision. */
+ SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
+ reb->wc_ctx, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t src_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ra_ev2_baton *reb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
+ if (!local_abspath)
+ {
+ *kind = svn_node_unknown;
+ return SVN_NO_ERROR;
+ }
+
+ /* ### what to do with SRC_REVISION? */
+
+ SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath,
+ FALSE, FALSE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+void *
+svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx,
+ apr_hash_t *relpath_map,
+ apr_pool_t *result_pool)
+{
+ struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb));
+
+ SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL);
+ SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL);
+
+ reb->wc_ctx = wc_ctx;
+ reb->relpath_map = relpath_map;
+
+ return reb;
+}
OpenPOWER on IntegriCloud