summaryrefslogtreecommitdiff
path: root/subversion/libsvn_ra_local
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
commitf1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch)
tree4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_ra_local
downloadsubversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz
Tarball conversion
Diffstat (limited to 'subversion/libsvn_ra_local')
-rw-r--r--subversion/libsvn_ra_local/ra_local.h97
-rw-r--r--subversion/libsvn_ra_local/ra_plugin.c1589
-rw-r--r--subversion/libsvn_ra_local/split_url.c78
3 files changed, 1764 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_local/ra_local.h b/subversion/libsvn_ra_local/ra_local.h
new file mode 100644
index 0000000..df455da
--- /dev/null
+++ b/subversion/libsvn_ra_local/ra_local.h
@@ -0,0 +1,97 @@
+/*
+ * ra_local.h : shared internal declarations for ra_local module
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_RA_LOCAL_H
+#define SVN_LIBSVN_RA_LOCAL_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_ra.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Structures **/
+
+/* A baton which represents a single ra_local session. */
+typedef struct svn_ra_local__session_baton_t
+{
+ /* The user accessing the repository. */
+ const char *username;
+
+ /* The URL of the session, split into two components. */
+ const char *repos_url;
+ svn_stringbuf_t *fs_path; /* URI-decoded, always with a leading slash. */
+
+ /* A repository object. */
+ svn_repos_t *repos;
+
+ /* The filesystem object associated with REPOS above (for
+ convenience). */
+ svn_fs_t *fs;
+
+ /* The UUID associated with REPOS above (cached) */
+ const char *uuid;
+
+ /* Callbacks/baton passed to svn_ra_open. */
+ const svn_ra_callbacks2_t *callbacks;
+ void *callback_baton;
+} svn_ra_local__session_baton_t;
+
+
+
+
+/** Private routines **/
+
+
+
+
+/* Given a `file://' URL, figure out which portion specifies a
+ repository on local disk, and return that in REPOS_URL (if not
+ NULL); URI-decode and return the remainder (the path *within* the
+ repository's filesystem) in FS_PATH. Open REPOS to the repository
+ root (if not NULL). Allocate the return values in POOL.
+ Currently, we are not expecting to handle `file://hostname/'-type
+ URLs; hostname, in this case, is expected to be the empty string or
+ "localhost". */
+svn_error_t *
+svn_ra_local__split_URL(svn_repos_t **repos,
+ const char **repos_url,
+ const char **fs_path,
+ const char *URL,
+ apr_pool_t *pool);
+
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_RA_LOCAL_H */
diff --git a/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c
new file mode 100644
index 0000000..58892e9
--- /dev/null
+++ b/subversion/libsvn_ra_local/ra_plugin.c
@@ -0,0 +1,1589 @@
+/*
+ * ra_plugin.c : the main RA module for local repository access
+ *
+ * ====================================================================
+ * 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 "ra_local.h"
+#include "svn_ra.h"
+#include "svn_fs.h"
+#include "svn_delta.h"
+#include "svn_repos.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_path.h"
+#include "svn_version.h"
+#include "svn_cache_config.h"
+
+#include "svn_private_config.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_fspath.h"
+#include "private/svn_atomic.h"
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+/*----------------------------------------------------------------*/
+
+/*** Miscellaneous helper functions ***/
+
+
+/* Pool cleanup handler: ensure that the access descriptor of the
+ filesystem (svn_fs_t *) DATA is set to NULL. */
+static apr_status_t
+cleanup_access(void *data)
+{
+ svn_error_t *serr;
+ svn_fs_t *fs = data;
+
+ serr = svn_fs_set_access(fs, NULL);
+
+ if (serr)
+ {
+ apr_status_t apr_err = serr->apr_err;
+ svn_error_clear(serr);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Fetch a username for use with SESS, and store it in SESS->username. */
+static svn_error_t *
+get_username(svn_ra_session_t *session,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_auth_iterstate_t *iterstate;
+ svn_fs_access_t *access_ctx;
+
+ /* If we've already found the username don't ask for it again. */
+ if (! sess->username)
+ {
+ /* Get a username somehow, so we have some svn:author property to
+ attach to a commit. */
+ if (sess->callbacks->auth_baton)
+ {
+ void *creds;
+ svn_auth_cred_username_t *username_creds;
+ SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
+ SVN_AUTH_CRED_USERNAME,
+ sess->uuid, /* realmstring */
+ sess->callbacks->auth_baton,
+ pool));
+
+ /* No point in calling next_creds(), since that assumes that the
+ first_creds() somehow failed to authenticate. But there's no
+ challenge going on, so we use whatever creds we get back on
+ the first try. */
+ username_creds = creds;
+ if (username_creds && username_creds->username)
+ {
+ sess->username = apr_pstrdup(session->pool,
+ username_creds->username);
+ svn_error_clear(svn_auth_save_credentials(iterstate, pool));
+ }
+ else
+ sess->username = "";
+ }
+ else
+ sess->username = "";
+ }
+
+ /* If we have a real username, attach it to the filesystem so that it can
+ be used to validate locks. Even if there already is a user context
+ associated, it may contain irrelevant lock tokens, so always create a new.
+ */
+ if (*sess->username)
+ {
+ SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
+ session->pool));
+ SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));
+
+ /* Make sure this context is disassociated when the pool gets
+ destroyed. */
+ apr_pool_cleanup_register(session->pool, sess->fs, cleanup_access,
+ apr_pool_cleanup_null);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements an svn_atomic__init_once callback. Sets the FSFS memory
+ cache size. */
+static svn_error_t *
+cache_init(void *baton, apr_pool_t *pool)
+{
+ apr_hash_t *config_hash = baton;
+ svn_config_t *config = NULL;
+ const char *memory_cache_size_str;
+
+ if (config_hash)
+ config = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+ svn_config_get(config, &memory_cache_size_str, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_MEMORY_CACHE_SIZE, NULL);
+ if (memory_cache_size_str)
+ {
+ apr_uint64_t memory_cache_size;
+ svn_cache_config_t settings = *svn_cache_config_get();
+
+ SVN_ERR(svn_error_quick_wrap(svn_cstring_atoui64(&memory_cache_size,
+ memory_cache_size_str),
+ _("memory-cache-size invalid")));
+ settings.cache_size = 1024 * 1024 * memory_cache_size;
+ svn_cache_config_set(&settings);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------*/
+
+/*** The reporter vtable needed by do_update() and friends ***/
+
+typedef struct reporter_baton_t
+{
+ svn_ra_local__session_baton_t *sess;
+ void *report_baton;
+
+} reporter_baton_t;
+
+
+static void *
+make_reporter_baton(svn_ra_local__session_baton_t *sess,
+ void *report_baton,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
+ rbaton->sess = sess;
+ rbaton->report_baton = report_baton;
+ return rbaton;
+}
+
+
+static svn_error_t *
+reporter_set_path(void *reporter_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_set_path3(rbaton->report_baton, path,
+ revision, depth, start_empty, lock_token, pool);
+}
+
+
+static svn_error_t *
+reporter_delete_path(void *reporter_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_delete_path(rbaton->report_baton, path, pool);
+}
+
+
+static svn_error_t *
+reporter_link_path(void *reporter_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ const char *fs_path = NULL;
+ const char *repos_url = rbaton->sess->repos_url;
+
+ if (!svn_uri__is_ancestor(repos_url, url))
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s'\n"
+ "is not the same repository as\n"
+ "'%s'"), url, rbaton->sess->repos_url);
+
+ /* Skip the repos_url, but keep the last '/' to create an fspath */
+ fs_path = svn_uri_skip_ancestor(repos_url, url, pool);
+ if (fs_path[0] == '\0')
+ fs_path = "/";
+ else
+ fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);
+
+ return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
+ depth, start_empty, lock_token, pool);
+}
+
+
+static svn_error_t *
+reporter_finish_report(void *reporter_baton,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_finish_report(rbaton->report_baton, pool);
+}
+
+
+static svn_error_t *
+reporter_abort_report(void *reporter_baton,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_abort_report(rbaton->report_baton, pool);
+}
+
+
+static const svn_ra_reporter3_t ra_local_reporter =
+{
+ reporter_set_path,
+ reporter_delete_path,
+ reporter_link_path,
+ reporter_finish_report,
+ reporter_abort_report
+};
+
+
+static svn_error_t *
+make_reporter(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *target,
+ const char *other_url,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ void *rbaton;
+ const char *other_fs_path = NULL;
+
+ /* Get the HEAD revision if one is not supplied. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
+
+ /* If OTHER_URL was provided, validate it and convert it into a
+ regular filesystem path. */
+ if (other_url)
+ {
+ const char *other_relpath
+ = svn_uri_skip_ancestor(sess->repos_url, other_url, pool);
+
+ /* Sanity check: the other_url better be in the same repository as
+ the original session url! */
+ if (! other_relpath)
+ return svn_error_createf
+ (SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s'\n"
+ "is not the same repository as\n"
+ "'%s'"), other_url, sess->repos_url);
+
+ other_fs_path = apr_pstrcat(pool, "/", other_relpath, (char *)NULL);
+ }
+
+ /* Pass back our reporter */
+ *reporter = &ra_local_reporter;
+
+ SVN_ERR(get_username(session, pool));
+
+ if (sess->callbacks)
+ SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
+ sess->callback_baton,
+ editor,
+ edit_baton,
+ &editor,
+ &edit_baton,
+ pool));
+
+ /* Build a reporter baton. */
+ SVN_ERR(svn_repos_begin_report2(&rbaton,
+ revision,
+ sess->repos,
+ sess->fs_path->data,
+ target,
+ other_fs_path,
+ text_deltas,
+ depth,
+ ignore_ancestry,
+ send_copyfrom_args,
+ editor,
+ edit_baton,
+ NULL,
+ NULL,
+ pool));
+
+ /* Wrap the report baton given us by the repos layer with our own
+ reporter baton. */
+ *report_baton = make_reporter_baton(sess, rbaton, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/*** Deltification stuff for get_commit_editor() ***/
+
+struct deltify_etc_baton
+{
+ svn_fs_t *fs; /* the fs to deltify in */
+ svn_repos_t *repos; /* repos for unlocking */
+ const char *fs_path; /* fs-path part of split session URL */
+ apr_hash_t *lock_tokens; /* tokens to unlock, if any */
+ apr_pool_t *pool; /* pool for scratch work */
+ svn_commit_callback2_t callback; /* the original callback */
+ void *callback_baton; /* the original callback's baton */
+};
+
+/* This implements 'svn_commit_callback_t'. Its invokes the original
+ (wrapped) callback, but also does deltification on the new revision and
+ possibly unlocks committed paths.
+ BATON is 'struct deltify_etc_baton *'. */
+static svn_error_t *
+deltify_etc(const svn_commit_info_t *commit_info,
+ void *baton, apr_pool_t *pool)
+{
+ struct deltify_etc_baton *db = baton;
+ svn_error_t *err1 = SVN_NO_ERROR;
+ svn_error_t *err2;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ /* Invoke the original callback first, in case someone's waiting to
+ know the revision number so they can go off and annotate an
+ issue or something. */
+ if (db->callback)
+ err1 = db->callback(commit_info, db->callback_baton, pool);
+
+ /* Maybe unlock the paths. */
+ if (db->lock_tokens)
+ {
+ iterpool = svn_pool_create(db->pool);
+ for (hi = apr_hash_first(db->pool, db->lock_tokens); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *rel_path;
+ void *val;
+ const char *abs_path, *token;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &rel_path, NULL, &val);
+ token = val;
+ abs_path = svn_fspath__join(db->fs_path, rel_path, iterpool);
+ /* We may get errors here if the lock was broken or stolen
+ after the commit succeeded. This is fine and should be
+ ignored. */
+ svn_error_clear(svn_repos_fs_unlock(db->repos, abs_path, token,
+ FALSE, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ /* But, deltification shouldn't be stopped just because someone's
+ random callback failed, so proceed unconditionally on to
+ deltification. */
+ err2 = svn_fs_deltify_revision(db->fs, commit_info->revision, db->pool);
+
+ /* It's more interesting if the original callback failed, so let
+ that one dominate. */
+ if (err1)
+ {
+ svn_error_clear(err2);
+ return err1;
+ }
+
+ return err2;
+}
+
+/*----------------------------------------------------------------*/
+
+/*** The RA vtable routines ***/
+
+#define RA_LOCAL_DESCRIPTION \
+ N_("Module for accessing a repository on local disk.")
+
+static const char *
+svn_ra_local__get_description(void)
+{
+ return _(RA_LOCAL_DESCRIPTION);
+}
+
+static const char * const *
+svn_ra_local__get_schemes(apr_pool_t *pool)
+{
+ static const char *schemes[] = { "file", NULL };
+
+ return schemes;
+}
+
+/* Do nothing.
+ *
+ * Why is this acceptable? FS warnings used to be used for only
+ * two things: failures to close BDB repositories and failures to
+ * interact with memcached in FSFS (new in 1.6). In 1.5 and earlier,
+ * we did not call svn_fs_set_warning_func in ra_local, which means
+ * that any BDB-closing failure would have led to abort()s; the fact
+ * that this hasn't led to huge hues and cries makes it seem likely
+ * that this just doesn't happen that often, at least not through
+ * ra_local. And as far as memcached goes, it seems unlikely that
+ * somebody is going to go through the trouble of setting up and
+ * running memcached servers but then use ra_local access. So we
+ * ignore errors here, so that memcached can use the FS warnings API
+ * without crashing ra_local.
+ */
+static void
+ignore_warnings(void *baton,
+ svn_error_t *err)
+{
+#ifdef SVN_DEBUG
+ SVN_DBG(("Ignoring FS warning %d\n", err ? err->apr_err : 0));
+#endif
+ return;
+}
+
+static svn_error_t *
+svn_ra_local__open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *repos_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess;
+ const char *fs_path;
+ static volatile svn_atomic_t cache_init_state = 0;
+
+ /* Initialise the FSFS memory cache size. We can only do this once
+ so one CONFIG will win the race and all others will be ignored
+ silently. */
+ SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool));
+
+ /* We don't support redirections in ra-local. */
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ /* Allocate and stash the session_sess args we have already. */
+ sess = apr_pcalloc(pool, sizeof(*sess));
+ sess->callbacks = callbacks;
+ sess->callback_baton = callback_baton;
+
+ /* Look through the URL, figure out which part points to the
+ repository, and which part is the path *within* the
+ repository. */
+ SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
+ &(sess->repos_url),
+ &fs_path,
+ repos_URL,
+ session->pool),
+ _("Unable to open an ra_local session to URL"));
+ sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
+
+ /* Cache the filesystem object from the repos here for
+ convenience. */
+ sess->fs = svn_repos_fs(sess->repos);
+
+ /* Ignore FS warnings. */
+ svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);
+
+ /* Cache the repository UUID as well */
+ SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
+
+ /* Be sure username is NULL so we know to look it up / ask for it */
+ sess->username = NULL;
+
+ session->priv = sess;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__reparent(svn_ra_session_t *session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *relpath = "";
+
+ /* If the new URL isn't the same as our repository root URL, then
+ let's ensure that it's some child of it. */
+ if (strcmp(url, sess->repos_url) != 0)
+ relpath = svn_uri__is_child(sess->repos_url, url, pool);
+ if (! relpath)
+ return svn_error_createf
+ (SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' is not a child of the session's repository root "
+ "URL '%s'"), url, sess->repos_url);
+
+ /* Update our FS_PATH sess member to point to our new
+ relative-URL-turned-absolute-filesystem-path. */
+ svn_stringbuf_set(sess->fs_path,
+ svn_fspath__canonicalize(relpath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_session_url(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ *url = svn_path_url_add_component2(sess->repos_url,
+ sess->fs_path->data + 1,
+ pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
+ include_merged_revisions, NULL, NULL,
+ handler, handler_baton, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_dated_revision(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_repos_dated_revision(revision, sess->repos, tm, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__change_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+
+ SVN_ERR(get_username(session, pool));
+ return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
+ name, old_value_p, value, TRUE, TRUE,
+ NULL, NULL, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_uuid(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ *uuid = sess->uuid;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_repos_root(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ *url = sess->repos_url;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__rev_proplist(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_repos_fs_revision_proplist(props, sess->repos, rev,
+ NULL, NULL, pool);
+}
+
+static svn_error_t *
+svn_ra_local__rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
+ NULL, NULL, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_commit_editor(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ struct deltify_etc_baton *db = apr_palloc(pool, sizeof(*db));
+ apr_hash_index_t *hi;
+ svn_fs_access_t *fs_access;
+
+ db->fs = sess->fs;
+ db->repos = sess->repos;
+ db->fs_path = sess->fs_path->data;
+ if (! keep_locks)
+ db->lock_tokens = lock_tokens;
+ else
+ db->lock_tokens = NULL;
+ db->pool = pool;
+ db->callback = callback;
+ db->callback_baton = callback_baton;
+
+ SVN_ERR(get_username(session, pool));
+
+ /* If there are lock tokens to add, do so. */
+ if (lock_tokens)
+ {
+ SVN_ERR(svn_fs_get_access(&fs_access, sess->fs));
+
+ /* If there is no access context, the filesystem will scream if a
+ lock is needed. */
+ if (fs_access)
+ {
+ for (hi = apr_hash_first(pool, lock_tokens); hi;
+ hi = apr_hash_next(hi))
+ {
+ void *val;
+ const char *abs_path, *token;
+ const void *key;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ abs_path = svn_fspath__join(sess->fs_path->data, key, pool);
+ token = val;
+ SVN_ERR(svn_fs_access_add_lock_token2(fs_access,
+ abs_path, token));
+ }
+ }
+ }
+
+ /* Copy the revprops table so we can add the username. */
+ revprop_table = apr_hash_copy(pool, revprop_table);
+ apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
+ svn_string_create(sess->username, pool));
+
+ /* Get the repos commit-editor */
+ return svn_repos_get_commit_editor5
+ (editor, edit_baton, sess->repos, NULL,
+ svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
+ revprop_table, deltify_etc, db, NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_mergeinfo_catalog_t tmp_catalog;
+ int i;
+ apr_array_header_t *abs_paths =
+ apr_array_make(pool, 0, sizeof(const char *));
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
+ APR_ARRAY_PUSH(abs_paths, const char *) =
+ svn_fspath__join(sess->fs_path->data, relative_path, pool);
+ }
+
+ SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog, sess->repos, abs_paths,
+ revision, inherit, include_descendants,
+ NULL, NULL, pool));
+ if (apr_hash_count(tmp_catalog) > 0)
+ SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
+ tmp_catalog,
+ sess->fs_path->data,
+ pool));
+ else
+ *catalog = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__do_update(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t update_revision,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ update_revision,
+ update_target,
+ NULL,
+ TRUE,
+ depth,
+ send_copyfrom_args,
+ FALSE,
+ update_editor,
+ update_baton,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_switch(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t update_revision,
+ const char *update_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ update_revision,
+ update_target,
+ switch_url,
+ TRUE,
+ depth,
+ FALSE, /* ### TODO(sussman): take new arg */
+ TRUE,
+ update_editor,
+ update_baton,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_status(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ revision,
+ status_target,
+ NULL,
+ FALSE,
+ depth,
+ FALSE,
+ FALSE,
+ status_editor,
+ status_baton,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_diff(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t update_revision,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *switch_url,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ update_revision,
+ update_target,
+ switch_url,
+ text_deltas,
+ depth,
+ FALSE,
+ ignore_ancestry,
+ update_editor,
+ update_baton,
+ pool);
+}
+
+
+struct log_baton
+{
+ svn_ra_local__session_baton_t *sess;
+ svn_log_entry_receiver_t real_cb;
+ void *real_baton;
+};
+
+static svn_error_t *
+cancellation_log_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_baton *b = baton;
+ svn_ra_local__session_baton_t *sess = b->sess;
+
+ SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
+
+ return b->real_cb(b->real_baton, log_entry, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__get_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ struct log_baton lb;
+ apr_array_header_t *abs_paths =
+ apr_array_make(pool, 0, sizeof(const char *));
+
+ if (paths)
+ {
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
+ APR_ARRAY_PUSH(abs_paths, const char *) =
+ svn_fspath__join(sess->fs_path->data, relative_path, pool);
+ }
+ }
+
+ if (sess->callbacks &&
+ sess->callbacks->cancel_func)
+ {
+ lb.real_cb = receiver;
+ lb.real_baton = receiver_baton;
+ lb.sess = sess;
+
+ receiver = cancellation_log_receiver;
+ receiver_baton = &lb;
+ }
+
+ return svn_repos_get_logs4(sess->repos,
+ abs_paths,
+ start,
+ end,
+ limit,
+ discover_changed_paths,
+ strict_node_history,
+ include_merged_revisions,
+ revprops,
+ NULL, NULL,
+ receiver,
+ receiver_baton,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_check_path(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_fs_root_t *root;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ if (! SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+ return svn_fs_check_path(kind, root, abs_path, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__stat(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_fs_root_t *root;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ if (! SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+
+ return svn_repos_stat(dirent, root, abs_path, pool);
+}
+
+
+
+
+static svn_error_t *
+get_node_props(apr_hash_t **props,
+ svn_ra_local__session_baton_t *sess,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_revnum_t cmt_rev;
+ const char *cmt_date, *cmt_author;
+
+ /* Create a hash with props attached to the fs node. */
+ SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
+
+ /* Now add some non-tweakable metadata to the hash as well... */
+
+ /* The so-called 'entryprops' with info about CR & friends. */
+ SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
+ &cmt_author, root, path, pool));
+
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_COMMITTED_REV,
+ APR_HASH_KEY_STRING,
+ svn_string_createf(pool, "%ld", cmt_rev));
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_COMMITTED_DATE,
+ APR_HASH_KEY_STRING,
+ cmt_date ? svn_string_create(cmt_date, pool) : NULL);
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_LAST_AUTHOR,
+ APR_HASH_KEY_STRING,
+ cmt_author ? svn_string_create(cmt_author, pool) : NULL);
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_UUID,
+ APR_HASH_KEY_STRING,
+ svn_string_create(sess->uuid, pool));
+
+ /* We have no 'wcprops' in ra_local, but might someday. */
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Getting just one file. */
+static svn_error_t *
+svn_ra_local__get_file(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_stream_t *contents;
+ svn_revnum_t youngest_rev;
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ svn_node_kind_t node_kind;
+
+ /* Open the revision's root. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
+ if (fetched_rev != NULL)
+ *fetched_rev = youngest_rev;
+ }
+ else
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool));
+ if (node_kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), abs_path);
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
+ _("'%s' is not a file"), abs_path);
+ }
+
+ if (stream)
+ {
+ /* Get a stream representing the file's contents. */
+ SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
+
+ /* Now push data from the fs stream back at the caller's stream.
+ Note that this particular RA layer does not computing a
+ checksum as we go, and confirming it against the repository's
+ checksum when done. That's because it calls
+ svn_fs_file_contents() directly, which already checks the
+ stored checksum, and all we're doing here is writing bytes in
+ a loop. Truly, Nothing Can Go Wrong :-). But RA layers that
+ go over a network should confirm the checksum.
+
+ Note: we are not supposed to close the passed-in stream, so
+ disown the thing.
+ */
+ SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool),
+ sess->callbacks
+ ? sess->callbacks->cancel_func : NULL,
+ sess->callback_baton,
+ pool));
+ }
+
+ /* Handle props if requested. */
+ if (props)
+ SVN_ERR(get_node_props(props, sess, root, abs_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting a directory's entries */
+static svn_error_t *
+svn_ra_local__get_dir(svn_ra_session_t *session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_ra_local__session_baton_t *sess = session->priv;
+ apr_pool_t *subpool;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ /* Open the revision's root. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
+ if (fetched_rev != NULL)
+ *fetched_rev = youngest_rev;
+ }
+ else
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+
+ if (dirents)
+ {
+ /* Get the dir's entries. */
+ SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
+
+ /* Loop over the fs dirents, and build a hash of general
+ svn_dirent_t's. */
+ *dirents = apr_hash_make(pool);
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_hash_t *prophash;
+ const char *datestring, *entryname, *fullpath;
+ svn_fs_dirent_t *fs_entry;
+ svn_dirent_t *entry = apr_pcalloc(pool, sizeof(*entry));
+
+ svn_pool_clear(subpool);
+
+ apr_hash_this(hi, &key, NULL, &val);
+ entryname = (const char *) key;
+ fs_entry = (svn_fs_dirent_t *) val;
+
+ fullpath = svn_dirent_join(abs_path, entryname, subpool);
+
+ if (dirent_fields & SVN_DIRENT_KIND)
+ {
+ /* node kind */
+ entry->kind = fs_entry->kind;
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ /* size */
+ if (entry->kind == svn_node_dir)
+ entry->size = 0;
+ else
+ SVN_ERR(svn_fs_file_length(&(entry->size), root,
+ fullpath, subpool));
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ /* has_props? */
+ SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath,
+ subpool));
+ entry->has_props = (apr_hash_count(prophash) != 0);
+ }
+
+ if ((dirent_fields & SVN_DIRENT_TIME)
+ || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ || (dirent_fields & SVN_DIRENT_CREATED_REV))
+ {
+ /* created_rev & friends */
+ SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
+ &datestring,
+ &(entry->last_author),
+ root, fullpath, subpool));
+ if (datestring)
+ SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
+ pool));
+ if (entry->last_author)
+ entry->last_author = apr_pstrdup(pool, entry->last_author);
+ }
+
+ /* Store. */
+ apr_hash_set(*dirents, entryname, APR_HASH_KEY_STRING, entry);
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Handle props if requested. */
+ if (props)
+ SVN_ERR(get_node_props(props, sess, root, abs_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
+ peg_revision, location_revisions,
+ NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_repos_node_location_segments(sess->repos, abs_path,
+ peg_revision, start_rev, end_rev,
+ receiver, receiver_baton,
+ NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__lock(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* A username is absolutely required to lock a path. */
+ SVN_ERR(get_username(session, pool));
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ svn_lock_t *lock;
+ const void *key;
+ const char *path;
+ void *val;
+ svn_revnum_t *revnum;
+ const char *abs_path;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ revnum = val;
+
+ abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
+
+ /* This wrapper will call pre- and post-lock hooks. */
+ err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment,
+ FALSE /* not DAV comment */,
+ 0 /* no expiration */, *revnum, force,
+ iterpool);
+
+ if (err && !SVN_ERR_IS_LOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
+ err, iterpool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__unlock(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* A username is absolutely required to unlock a path. */
+ SVN_ERR(get_username(session, pool));
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path;
+ void *val;
+ const char *abs_path, *token;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ /* Since we can't store NULL values in a hash, we turn "" to
+ NULL here. */
+ if (strcmp(val, "") != 0)
+ token = val;
+ else
+ token = NULL;
+
+ abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
+
+ /* This wrapper will call pre- and post-unlock hooks. */
+ err = svn_repos_fs_unlock(sess->repos, abs_path, token, force,
+ iterpool);
+
+ if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+svn_ra_local__get_lock(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
+}
+
+
+
+static svn_error_t *
+svn_ra_local__get_locks(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ /* Kinda silly to call the repos wrapper, since we have no authz
+ func to give it. But heck, why not. */
+ return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth,
+ NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__replay(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
+ revision, pool));
+ return svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
+ send_deltas, editor, edit_baton, NULL, NULL,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__replay_range(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_local__has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+
+ if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0)
+ {
+ *has = TRUE;
+ }
+ else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
+ {
+ /* With mergeinfo, the code's capabilities may not reflect the
+ repository's, so inquire further. */
+ SVN_ERR(svn_repos_has_capability(sess->repos, has,
+ SVN_REPOS_CAPABILITY_MERGEINFO,
+ pool));
+ }
+ else /* Don't know any other capabilities, so error. */
+ {
+ return svn_error_createf
+ (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"), capability);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ SVN_ERR(svn_repos_deleted_rev(sess->fs,
+ abs_path,
+ peg_revision,
+ end_revision,
+ revision_deleted,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------*/
+
+static const svn_version_t *
+ra_local_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+/** The ra_vtable **/
+
+static const svn_ra__vtable_t ra_local_vtable =
+{
+ ra_local_version,
+ svn_ra_local__get_description,
+ svn_ra_local__get_schemes,
+ svn_ra_local__open,
+ svn_ra_local__reparent,
+ svn_ra_local__get_session_url,
+ svn_ra_local__get_latest_revnum,
+ svn_ra_local__get_dated_revision,
+ svn_ra_local__change_rev_prop,
+ svn_ra_local__rev_proplist,
+ svn_ra_local__rev_prop,
+ svn_ra_local__get_commit_editor,
+ svn_ra_local__get_file,
+ svn_ra_local__get_dir,
+ svn_ra_local__get_mergeinfo,
+ svn_ra_local__do_update,
+ svn_ra_local__do_switch,
+ svn_ra_local__do_status,
+ svn_ra_local__do_diff,
+ svn_ra_local__get_log,
+ svn_ra_local__do_check_path,
+ svn_ra_local__stat,
+ svn_ra_local__get_uuid,
+ svn_ra_local__get_repos_root,
+ svn_ra_local__get_locations,
+ svn_ra_local__get_location_segments,
+ svn_ra_local__get_file_revs,
+ svn_ra_local__lock,
+ svn_ra_local__unlock,
+ svn_ra_local__get_lock,
+ svn_ra_local__get_locks,
+ svn_ra_local__replay,
+ svn_ra_local__has_capability,
+ svn_ra_local__replay_range,
+ svn_ra_local__get_deleted_rev
+};
+
+
+/*----------------------------------------------------------------*/
+
+/** The One Public Routine, called by libsvn_ra **/
+
+svn_error_t *
+svn_ra_local__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { NULL, NULL }
+ };
+
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The RA loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported RA loader version (%d) for "
+ "ra_local"),
+ loader_version->major);
+
+ SVN_ERR(svn_ver_check_list(ra_local_version(), checklist));
+
+#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
+ /* This assumes that POOL was the pool used to load the dso. */
+ SVN_ERR(svn_fs_initialize(pool));
+#endif
+
+ *vtable = &ra_local_vtable;
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for the 1.1 and before API. */
+#define NAME "ra_local"
+#define DESCRIPTION RA_LOCAL_DESCRIPTION
+#define VTBL ra_local_vtable
+#define INITFUNC svn_ra_local__init
+#define COMPAT_INITFUNC svn_ra_local_init
+#include "../libsvn_ra/wrapper_template.h"
diff --git a/subversion/libsvn_ra_local/split_url.c b/subversion/libsvn_ra_local/split_url.c
new file mode 100644
index 0000000..639e0ca
--- /dev/null
+++ b/subversion/libsvn_ra_local/split_url.c
@@ -0,0 +1,78 @@
+/*
+ * checkout.c : read a repository and drive a checkout editor.
+ *
+ * ====================================================================
+ * 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 "ra_local.h"
+#include <string.h>
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+#include "svn_private_config.h"
+
+
+svn_error_t *
+svn_ra_local__split_URL(svn_repos_t **repos,
+ const char **repos_url,
+ const char **fs_path,
+ const char *URL,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ const char *repos_dirent;
+ const char *repos_root_dirent;
+ svn_stringbuf_t *urlbuf;
+
+ SVN_ERR(svn_uri_get_dirent_from_file_url(&repos_dirent, URL, pool));
+
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(repos_dirent, pool);
+ if (!repos_root_dirent)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, NULL,
+ _("Unable to open repository '%s'"), URL);
+
+ /* Attempt to open a repository at URL. */
+ err = svn_repos_open2(repos, repos_root_dirent, NULL, pool);
+ if (err)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, err,
+ _("Unable to open repository '%s'"), URL);
+
+ /* Assert capabilities directly, since client == server. */
+ {
+ apr_array_header_t *caps = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(caps, const char *) = SVN_RA_CAPABILITY_MERGEINFO;
+ SVN_ERR(svn_repos_remember_client_capabilities(*repos, caps));
+ }
+
+ *fs_path = &repos_dirent[strlen(repos_root_dirent)];
+
+ if (**fs_path == '\0')
+ *fs_path = "/";
+
+ /* Remove the path components in *fs_path from the original URL, to get
+ the URL to the repository root. */
+ urlbuf = svn_stringbuf_create(URL, pool);
+ svn_path_remove_components(urlbuf,
+ svn_path_component_count(repos_dirent)
+ - svn_path_component_count(repos_root_dirent));
+ *repos_url = urlbuf->data;
+
+ return SVN_NO_ERROR;
+}