summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_svn/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_svn/client.c')
-rw-r--r--subversion/libsvn_ra_svn/client.c2739
1 files changed, 2739 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c
new file mode 100644
index 0000000..4b3762c
--- /dev/null
+++ b/subversion/libsvn_ra_svn/client.c
@@ -0,0 +1,2739 @@
+/*
+ * client.c : Functions for repository access via the Subversion protocol
+ *
+ * ====================================================================
+ * 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 "svn_private_config.h"
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_network_io.h>
+#include <apr_uri.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_time.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_config.h"
+#include "svn_ra.h"
+#include "svn_ra_svn.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_version.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_fspath.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_svn.h"
+
+#ifdef SVN_HAVE_SASL
+#define DO_AUTH svn_ra_svn__do_cyrus_auth
+#else
+#define DO_AUTH svn_ra_svn__do_internal_auth
+#endif
+
+/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
+ whatever reason) deems svn_depth_immediates as non-recursive, which
+ is ... kinda true, but not true enough for our purposes. We need
+ our requested recursion level to be *at least* as recursive as the
+ real depth we're looking for.
+ */
+#define DEPTH_TO_RECURSE(d) \
+ ((d) == svn_depth_unknown || (d) > svn_depth_files)
+
+typedef struct ra_svn_commit_callback_baton_t {
+ svn_ra_svn__session_baton_t *sess_baton;
+ apr_pool_t *pool;
+ svn_revnum_t *new_rev;
+ svn_commit_callback2_t callback;
+ void *callback_baton;
+} ra_svn_commit_callback_baton_t;
+
+typedef struct ra_svn_reporter_baton_t {
+ svn_ra_svn__session_baton_t *sess_baton;
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+} ra_svn_reporter_baton_t;
+
+/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
+ portion. */
+static void parse_tunnel(const char *url, const char **tunnel,
+ apr_pool_t *pool)
+{
+ *tunnel = NULL;
+
+ if (strncasecmp(url, "svn", 3) != 0)
+ return;
+ url += 3;
+
+ /* Get the tunnel specification, if any. */
+ if (*url == '+')
+ {
+ const char *p;
+
+ url++;
+ p = strchr(url, ':');
+ if (!p)
+ return;
+ *tunnel = apr_pstrmemdup(pool, url, p - url);
+ }
+}
+
+static svn_error_t *make_connection(const char *hostname, unsigned short port,
+ apr_socket_t **sock, apr_pool_t *pool)
+{
+ apr_sockaddr_t *sa;
+ apr_status_t status;
+ int family = APR_INET;
+
+ /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
+ APR_UNSPEC, because it may give us back an IPV6 address even if we can't
+ create IPV6 sockets. */
+
+#if APR_HAVE_IPV6
+#ifdef MAX_SECS_TO_LINGER
+ status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
+ APR_PROTO_TCP, pool);
+#endif
+ if (status == 0)
+ {
+ apr_socket_close(*sock);
+ family = APR_UNSPEC;
+ }
+#endif
+
+ /* Resolve the hostname. */
+ status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
+ if (status)
+ return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
+ hostname);
+ /* Iterate through the returned list of addresses attempting to
+ * connect to each in turn. */
+ do
+ {
+ /* Create the socket. */
+#ifdef MAX_SECS_TO_LINGER
+ /* ### old APR interface */
+ status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
+ pool);
+#endif
+ if (status == APR_SUCCESS)
+ {
+ status = apr_socket_connect(*sock, sa);
+ if (status != APR_SUCCESS)
+ apr_socket_close(*sock);
+ }
+ sa = sa->next;
+ }
+ while (status != APR_SUCCESS && sa);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
+ hostname);
+
+ /* Enable TCP keep-alives on the socket so we time out when
+ * the connection breaks due to network-layer problems.
+ * If the peer has dropped the connection due to a network partition
+ * or a crash, or if the peer no longer considers the connection
+ * valid because we are behind a NAT and our public IP has changed,
+ * it will respond to the keep-alive probe with a RST instead of an
+ * acknowledgment segment, which will cause svn to abort the session
+ * even while it is currently blocked waiting for data from the peer.
+ * See issue #3347. */
+ status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
+ if (status)
+ {
+ /* It's not a fatal error if we cannot enable keep-alives. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
+ property diffs in LIST, received from the server. */
+static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ apr_array_header_t **diffs)
+{
+ int i;
+
+ *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ svn_prop_t *prop;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Prop diffs element not a list"));
+ prop = apr_array_push(*diffs);
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
+ &prop->value));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Parse a lockdesc, provided in LIST as specified by the protocol into
+ LOCK, allocated in POOL. */
+static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
+ svn_lock_t **lock)
+{
+ const char *cdate, *edate;
+ *lock = svn_lock_create(pool);
+ SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
+ &(*lock)->token, &(*lock)->owner,
+ &(*lock)->comment, &cdate, &edate));
+ (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
+ SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
+ if (edate)
+ SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
+ return SVN_NO_ERROR;
+}
+
+/* --- AUTHENTICATION ROUTINES --- */
+
+svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *mech, const char *mech_arg)
+{
+ return svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg);
+}
+
+static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
+ apr_pool_t *pool)
+{
+ svn_ra_svn_conn_t *conn = sess->conn;
+ apr_array_header_t *mechlist;
+ const char *realm;
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
+ if (mechlist->nelts == 0)
+ return SVN_NO_ERROR;
+ return DO_AUTH(sess, mechlist, realm, pool);
+}
+
+/* --- REPORTER IMPLEMENTATION --- */
+
+static svn_error_t *ra_svn_set_path(void *baton, const char *path,
+ svn_revnum_t rev,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
+ start_empty, lock_token, depth));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_link_path(void *baton, const char *path,
+ const char *url,
+ svn_revnum_t rev,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
+ start_empty, lock_token, depth));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_finish_report(void *baton,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
+ SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
+ SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
+ NULL, FALSE));
+ SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_abort_report(void *baton,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_ra_reporter3_t ra_svn_reporter = {
+ ra_svn_set_path,
+ ra_svn_delete_path,
+ ra_svn_link_path,
+ ra_svn_finish_report,
+ ra_svn_abort_report
+};
+
+/* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
+ * EDITOR/EDIT_BATON when it gets the finish_report() call.
+ *
+ * Allocate the new reporter in POOL.
+ */
+static svn_error_t *
+ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
+ apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const char *target,
+ svn_depth_t depth,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton)
+{
+ ra_svn_reporter_baton_t *b;
+ const svn_delta_editor_t *filter_editor;
+ void *filter_baton;
+
+ /* We can skip the depth filtering when the user requested
+ depth_files or depth_infinity because the server will
+ transmit the right stuff anyway. */
+ if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
+ && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
+ {
+ SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
+ &filter_baton,
+ editor, edit_baton, depth,
+ *target != '\0',
+ pool));
+ editor = filter_editor;
+ edit_baton = filter_baton;
+ }
+
+ b = apr_palloc(pool, sizeof(*b));
+ b->sess_baton = sess_baton;
+ b->conn = sess_baton->conn;
+ b->pool = pool;
+ b->editor = editor;
+ b->edit_baton = edit_baton;
+
+ *reporter = &ra_svn_reporter;
+ *report_baton = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* --- RA LAYER IMPLEMENTATION --- */
+
+/* (Note: *ARGV is an output parameter.) */
+static svn_error_t *find_tunnel_agent(const char *tunnel,
+ const char *hostinfo,
+ const char ***argv,
+ apr_hash_t *config, apr_pool_t *pool)
+{
+ svn_config_t *cfg;
+ const char *val, *var, *cmd;
+ char **cmd_argv;
+ apr_size_t len;
+ apr_status_t status;
+ int n;
+
+ /* Look up the tunnel specification in config. */
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
+
+ /* We have one predefined tunnel scheme, if it isn't overridden by config. */
+ if (!val && strcmp(tunnel, "ssh") == 0)
+ {
+ /* Killing the tunnel agent with SIGTERM leads to unsightly
+ * stderr output from ssh, unless we pass -q.
+ * The "-q" option to ssh is widely supported: all versions of
+ * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
+ * versions have it too. If the user is using some other ssh
+ * implementation that doesn't accept it, they can override it
+ * in the [tunnels] section of the config. */
+ val = "$SVN_SSH ssh -q";
+ }
+
+ if (!val || !*val)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ _("Undefined tunnel scheme '%s'"), tunnel);
+
+ /* If the scheme definition begins with "$varname", it means there
+ * is an environment variable which can override the command. */
+ if (*val == '$')
+ {
+ val++;
+ len = strcspn(val, " ");
+ var = apr_pstrmemdup(pool, val, len);
+ cmd = getenv(var);
+ if (!cmd)
+ {
+ cmd = val + len;
+ while (*cmd == ' ')
+ cmd++;
+ if (!*cmd)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ _("Tunnel scheme %s requires environment "
+ "variable %s to be defined"), tunnel,
+ var);
+ }
+ }
+ else
+ cmd = val;
+
+ /* Tokenize the command into a list of arguments. */
+ status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
+ if (status != APR_SUCCESS)
+ return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
+
+ /* Append the fixed arguments to the result. */
+ for (n = 0; cmd_argv[n] != NULL; n++)
+ ;
+ *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
+ memcpy((void *) *argv, cmd_argv, n * sizeof(char *));
+ (*argv)[n++] = svn_path_uri_decode(hostinfo, pool);
+ (*argv)[n++] = "svnserve";
+ (*argv)[n++] = "-t";
+ (*argv)[n] = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* This function handles any errors which occur in the child process
+ * created for a tunnel agent. We write the error out as a command
+ * failure; the code in ra_svn_open() to read the server's greeting
+ * will see the error and return it to the caller. */
+static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
+ const char *desc)
+{
+ svn_ra_svn_conn_t *conn;
+ apr_file_t *in_file, *out_file;
+ svn_error_t *err;
+
+ if (apr_file_open_stdin(&in_file, pool)
+ || apr_file_open_stdout(&out_file, pool))
+ return;
+
+ conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
+ 0, pool);
+ err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
+ svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
+ svn_error_clear(err);
+ svn_error_clear(svn_ra_svn__flush(conn, pool));
+}
+
+/* (Note: *CONN is an output parameter.) */
+static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ apr_proc_t *proc;
+ apr_procattr_t *attr;
+ svn_error_t *err;
+
+ status = apr_procattr_create(&attr, pool);
+ if (status == APR_SUCCESS)
+ status = apr_procattr_io_set(attr, 1, 1, 0);
+ if (status == APR_SUCCESS)
+ status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
+ if (status == APR_SUCCESS)
+ status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
+ proc = apr_palloc(pool, sizeof(*proc));
+ if (status == APR_SUCCESS)
+ status = apr_proc_create(proc, *args, args, NULL, attr, pool);
+ if (status != APR_SUCCESS)
+ return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
+ svn_error_wrap_apr(status,
+ _("Can't create tunnel")), NULL);
+
+ /* Arrange for the tunnel agent to get a SIGTERM on pool
+ * cleanup. This is a little extreme, but the alternatives
+ * weren't working out.
+ *
+ * Closing the pipes and waiting for the process to die
+ * was prone to mysterious hangs which are difficult to
+ * diagnose (e.g. svnserve dumps core due to unrelated bug;
+ * sshd goes into zombie state; ssh connection is never
+ * closed; ssh never terminates).
+ * See also the long dicussion in issue #2580 if you really
+ * want to know various reasons for these problems and
+ * the different opinions on this issue.
+ *
+ * On Win32, APR does not support KILL_ONLY_ONCE. It only has
+ * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
+ * KILL_ALWAYS, which immediately calls TerminateProcess().
+ * This instantly kills the tunnel, leaving sshd and svnserve
+ * on a remote machine running indefinitely. These processes
+ * accumulate. The problem is most often seen with a fast client
+ * machine and a modest internet connection, as the tunnel
+ * is killed before being able to gracefully complete the
+ * session. In that case, svn is unusable 100% of the time on
+ * the windows machine. Thus, on Win32, we use KILL_NEVER and
+ * take the lesser of two evils.
+ */
+#ifdef WIN32
+ apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
+#else
+ apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
+#endif
+
+ /* APR pipe objects inherit by default. But we don't want the
+ * tunnel agent's pipes held open by future child processes
+ * (such as other ra_svn sessions), so turn that off. */
+ apr_file_inherit_unset(proc->in);
+ apr_file_inherit_unset(proc->out);
+
+ /* Guard against dotfile output to stdout on the server. */
+ *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ 0, 0, pool);
+ err = svn_ra_svn__skip_leading_garbage(*conn, pool);
+ if (err)
+ return svn_error_quick_wrap(
+ err,
+ _("To better debug SSH connection problems, remove the -q "
+ "option from 'ssh' in the [tunnels] section of your "
+ "Subversion configuration file."));
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse URL inot URI, validating it and setting the default port if none
+ was given. Allocate the URI fileds out of POOL. */
+static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+
+ apr_err = apr_uri_parse(pool, url, uri);
+
+ if (apr_err != 0)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal svn repository URL '%s'"), url);
+
+ if (! uri->port)
+ uri->port = SVN_RA_SVN_PORT;
+
+ return SVN_NO_ERROR;
+}
+
+/* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
+ URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
+ are provided by the caller of ra_svn_open. If tunnel_argv is non-null,
+ it points to a program argument list to use when invoking the tunnel agent.
+*/
+static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
+ const char *url,
+ const apr_uri_t *uri,
+ const char **tunnel_argv,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callbacks_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess;
+ svn_ra_svn_conn_t *conn;
+ apr_socket_t *sock;
+ apr_uint64_t minver, maxver;
+ apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
+ const char *client_string = NULL;
+
+ sess = apr_palloc(pool, sizeof(*sess));
+ sess->pool = pool;
+ sess->is_tunneled = (tunnel_argv != NULL);
+ sess->url = apr_pstrdup(pool, url);
+ sess->user = uri->user;
+ sess->hostname = uri->hostname;
+ sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
+ uri->port);
+ sess->tunnel_argv = tunnel_argv;
+ sess->callbacks = callbacks;
+ sess->callbacks_baton = callbacks_baton;
+ sess->bytes_read = sess->bytes_written = 0;
+
+ if (tunnel_argv)
+ SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
+ else
+ {
+ SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
+ conn = svn_ra_svn_create_conn3(sock, NULL, NULL,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ 0, 0, pool);
+ }
+
+ /* Build the useragent string, querying the client for any
+ customizations it wishes to note. For historical reasons, we
+ still deliver the hard-coded client version info
+ (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
+ separately in the protocol/capabilities handshake below. But the
+ commit logic wants the combined form for use with the
+ SVN_PROP_TXN_USER_AGENT ephemeral property because that's
+ consistent with our DAV approach. */
+ if (sess->callbacks->get_client_string != NULL)
+ SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
+ &client_string, pool));
+ if (client_string)
+ sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
+ client_string, (char *)NULL);
+ else
+ sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
+
+ /* Make sure we set conn->session before reading from it,
+ * because the reader and writer functions expect a non-NULL value. */
+ sess->conn = conn;
+ conn->session = sess;
+
+ /* Read server's greeting. */
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
+ &mechlist, &server_caplist));
+
+ /* We support protocol version 2. */
+ if (minver > 2)
+ return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server requires minimum version %d"),
+ (int) minver);
+ if (maxver < 2)
+ return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server only supports versions up to %d"),
+ (int) maxver);
+ SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
+
+ /* All released versions of Subversion support edit-pipeline,
+ * so we do not support servers that do not. */
+ if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
+ return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server does not support edit pipelining"));
+
+ /* In protocol version 2, we send back our protocol version, our
+ * capability list, and the URL, and subsequently there is an auth
+ * request. */
+ /* Client-side capabilities list: */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
+ (apr_uint64_t) 2,
+ SVN_RA_SVN_CAP_EDIT_PIPELINE,
+ SVN_RA_SVN_CAP_SVNDIFF1,
+ SVN_RA_SVN_CAP_ABSENT_ENTRIES,
+ SVN_RA_SVN_CAP_DEPTH,
+ SVN_RA_SVN_CAP_MERGEINFO,
+ SVN_RA_SVN_CAP_LOG_REVPROPS,
+ url,
+ SVN_RA_SVN__DEFAULT_USERAGENT,
+ client_string));
+ SVN_ERR(handle_auth_request(sess, pool));
+
+ /* This is where the security layer would go into effect if we
+ * supported security layers, which is a ways off. */
+
+ /* Read the repository's uuid and root URL, and perhaps learn more
+ capabilities that weren't available before now. */
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
+ &conn->repos_root, &repos_caplist));
+ if (repos_caplist)
+ SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
+
+ if (conn->repos_root)
+ {
+ conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
+ /* We should check that the returned string is a prefix of url, since
+ that's the API guarantee, but this isn't true for 1.0 servers.
+ Checking the length prevents client crashes. */
+ if (strlen(conn->repos_root) > strlen(url))
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Impossibly long repository root from "
+ "server"));
+ }
+
+ *sess_p = sess;
+
+ return SVN_NO_ERROR;
+}
+
+
+#ifdef SVN_HAVE_SASL
+#define RA_SVN_DESCRIPTION \
+ N_("Module for accessing a repository using the svn network protocol.\n" \
+ " - with Cyrus SASL authentication")
+#else
+#define RA_SVN_DESCRIPTION \
+ N_("Module for accessing a repository using the svn network protocol.")
+#endif
+
+static const char *ra_svn_get_description(void)
+{
+ return _(RA_SVN_DESCRIPTION);
+}
+
+static const char * const *
+ra_svn_get_schemes(apr_pool_t *pool)
+{
+ static const char *schemes[] = { "svn", NULL };
+
+ return schemes;
+}
+
+
+
+static svn_error_t *ra_svn_open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *url,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ apr_pool_t *sess_pool = svn_pool_create(pool);
+ svn_ra_svn__session_baton_t *sess;
+ const char *tunnel, **tunnel_argv;
+ apr_uri_t uri;
+ svn_config_t *cfg, *cfg_client;
+
+ /* We don't support server-prescribed redirections in ra-svn. */
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ SVN_ERR(parse_url(url, &uri, sess_pool));
+
+ parse_tunnel(url, &tunnel, pool);
+
+ if (tunnel)
+ SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
+ pool));
+ else
+ tunnel_argv = NULL;
+
+ cfg_client = config
+ ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
+
+ /* We open the session in a subpool so we can get rid of it if we
+ reparent with a server that doesn't support reparenting. */
+ SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
+ callbacks, callback_baton, sess_pool));
+ session->priv = sess;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = ra_session->priv;
+ svn_ra_svn_conn_t *conn = sess->conn;
+ svn_error_t *err;
+ apr_pool_t *sess_pool;
+ svn_ra_svn__session_baton_t *new_sess;
+ apr_uri_t uri;
+
+ SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
+ err = handle_auth_request(sess, pool);
+ if (! err)
+ {
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+ sess->url = apr_pstrdup(sess->pool, url);
+ return SVN_NO_ERROR;
+ }
+ else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ return err;
+
+ /* Servers before 1.4 doesn't support this command; try to reconnect
+ instead. */
+ svn_error_clear(err);
+ /* Create a new subpool of the RA session pool. */
+ sess_pool = svn_pool_create(ra_session->pool);
+ err = parse_url(url, &uri, sess_pool);
+ if (! err)
+ err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
+ sess->callbacks, sess->callbacks_baton, sess_pool);
+ /* We destroy the new session pool on error, since it is allocated in
+ the main session pool. */
+ if (err)
+ {
+ svn_pool_destroy(sess_pool);
+ return err;
+ }
+
+ /* We have a new connection, assign it and destroy the old. */
+ ra_session->priv = new_sess;
+ svn_pool_destroy(sess->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
+ const char **url, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ *url = apr_pstrdup(pool, sess->url);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
+ svn_revnum_t *rev, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
+ svn_revnum_t *rev, apr_time_t tm,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+/* Forward declaration. */
+static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+static svn_error_t *ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t dont_care;
+ const svn_string_t *old_value;
+ svn_boolean_t has_atomic_revprops;
+
+ SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
+ pool));
+
+ if (old_value_p)
+ {
+ /* How did you get past the same check in svn_ra_change_rev_prop2()? */
+ SVN_ERR_ASSERT(has_atomic_revprops);
+
+ dont_care = FALSE;
+ old_value = *old_value_p;
+ }
+ else
+ {
+ dont_care = TRUE;
+ old_value = NULL;
+ }
+
+ if (has_atomic_revprops)
+ SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
+ value, dont_care,
+ old_value));
+ else
+ SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
+ value));
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ *uuid = conn->uuid;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ if (!conn->repos_root)
+ return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server did not send repository root"));
+ *url = conn->repos_root;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
+ apr_hash_t **props, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *proplist;
+
+ SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
+ SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_end_commit(void *baton)
+{
+ ra_svn_commit_callback_baton_t *ccb = baton;
+ svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
+
+ SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
+ SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
+ "r(?c)(?c)?(?c)",
+ &(commit_info->revision),
+ &(commit_info->date),
+ &(commit_info->author),
+ &(commit_info->post_commit_err)));
+
+ if (ccb->callback)
+ SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_commit(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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ ra_svn_commit_callback_baton_t *ccb;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ const svn_string_t *log_msg = svn_hash_gets(revprop_table,
+ SVN_PROP_REVISION_LOG);
+
+ /* If we're sending revprops other than svn:log, make sure the server won't
+ silently ignore them. */
+ if (apr_hash_count(revprop_table) > 1 &&
+ ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+ _("Server doesn't support setting arbitrary "
+ "revision properties during commit"));
+
+ /* If the server supports ephemeral txnprops, add the one that
+ reports the client's version level string. */
+ if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
+ svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
+ {
+ svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
+ svn_string_create(SVN_VER_NUMBER, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
+ svn_string_create(sess_baton->useragent, pool));
+ }
+
+ /* Tell the server we're starting the commit.
+ Send log message here for backwards compatibility with servers
+ before 1.5. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
+ log_msg->data));
+ if (lock_tokens)
+ {
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const char *path, *token;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ token = val;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
+ }
+ svn_pool_destroy(iterpool);
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ /* Remember a few arguments for when the commit is over. */
+ ccb = apr_palloc(pool, sizeof(*ccb));
+ ccb->sess_baton = sess_baton;
+ ccb->pool = pool;
+ ccb->new_rev = NULL;
+ ccb->callback = callback;
+ ccb->callback_baton = callback_baton;
+
+ /* Fetch an editor for the caller to drive. The editor will call
+ * ra_svn_end_commit() upon close_edit(), at which point we'll fill
+ * in the new_rev, committed_date, and committed_author values. */
+ svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
+ ra_svn_end_commit, ccb);
+ return SVN_NO_ERROR;
+}
+
+/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
+ const char * repos relative paths and properties for those paths, storing
+ the result as an array of svn_prop_inherited_item_t *items. */
+static svn_error_t *
+parse_iproplist(apr_array_header_t **inherited_props,
+ const apr_array_header_t *iproplist,
+ svn_ra_session_t *session,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ int i;
+ const char *repos_root_url;
+ apr_pool_t *iterpool;
+
+ if (iproplist == NULL)
+ {
+ /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
+ capability we shouldn't be asking for inherited props, but if we
+ did and the server sent back nothing then we'll want to handle
+ that. */
+ *inherited_props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
+
+ *inherited_props = apr_array_make(
+ result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < iproplist->nelts; i++)
+ {
+ apr_array_header_t *iprop_list;
+ char *parent_rel_path;
+ apr_hash_t *iprops;
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
+ svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(
+ SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Inherited proplist element not a list"));
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
+ &parent_rel_path, &iprop_list));
+ SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
+ new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
+ parent_rel_path,
+ result_pool);
+ new_iprop->prop_hash = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(iterpool, iprops);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ svn_hash_sets(new_iprop->prop_hash,
+ apr_pstrdup(result_pool, name),
+ svn_string_dup(value, result_pool));
+ }
+ APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
+ new_iprop;
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
+ svn_revnum_t rev, svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *proplist;
+ const char *expected_digest;
+ svn_checksum_t *expected_checksum = NULL;
+ svn_checksum_ctx_t *checksum_ctx;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
+ (props != NULL), (stream != NULL)));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
+ &expected_digest,
+ &rev, &proplist));
+
+ if (fetched_rev)
+ *fetched_rev = rev;
+ if (props)
+ SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
+
+ /* We're done if the contents weren't wanted. */
+ if (!stream)
+ return SVN_NO_ERROR;
+
+ if (expected_digest)
+ {
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ expected_digest, pool));
+ checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ }
+
+ /* Read the file's contents. */
+ iterpool = svn_pool_create(pool);
+ while (1)
+ {
+ svn_ra_svn_item_t *item;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Non-string as part of file contents"));
+ if (item->u.string->len == 0)
+ break;
+
+ if (expected_checksum)
+ SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
+ item->u.string->len));
+
+ SVN_ERR(svn_stream_write(stream, item->u.string->data,
+ &item->u.string->len));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ if (expected_checksum)
+ {
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
+ if (!svn_checksum_match(checksum, expected_checksum))
+ return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
+ _("Checksum mismatch for '%s'"),
+ path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_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 rev,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *proplist, *dirlist;
+ int i;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
+ rev, (props != NULL), (dirents != NULL)));
+ if (dirent_fields & SVN_DIRENT_KIND)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
+ if (dirent_fields & SVN_DIRENT_CREATED_REV)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
+ if (dirent_fields & SVN_DIRENT_TIME)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
+ if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
+ &dirlist));
+
+ if (fetched_rev)
+ *fetched_rev = rev;
+ if (props)
+ SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
+
+ /* We're done if dirents aren't wanted. */
+ if (!dirents)
+ return SVN_NO_ERROR;
+
+ /* Interpret the directory list. */
+ *dirents = apr_hash_make(pool);
+ for (i = 0; i < dirlist->nelts; i++)
+ {
+ const char *name, *kind, *cdate, *cauthor;
+ svn_boolean_t has_props;
+ svn_dirent_t *dirent;
+ apr_uint64_t size;
+ svn_revnum_t crev;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Dirlist element not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
+ &name, &kind, &size, &has_props,
+ &crev, &cdate, &cauthor));
+ name = svn_relpath_canonicalize(name, pool);
+ dirent = svn_dirent_create(pool);
+ dirent->kind = svn_node_kind_from_word(kind);
+ dirent->size = size;/* FIXME: svn_filesize_t */
+ dirent->has_props = has_props;
+ dirent->created_rev = crev;
+ /* NOTE: the tuple's format string says CDATE may be NULL. But this
+ function does not allow that. The server has always sent us some
+ random date, however, so this just happens to work. But let's
+ be wary of servers that are (improperly) fixed to send NULL.
+
+ Note: they should NOT be "fixed" to send NULL, as that would break
+ any older clients which received that NULL. But we may as well
+ be defensive against a malicous server. */
+ if (cdate == NULL)
+ dirent->time = 0;
+ else
+ SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
+ dirent->last_author = cauthor;
+ svn_hash_sets(*dirents, name, dirent);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Converts a apr_uint64_t with values TRUE, FALSE or
+ SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
+ to a svn_tristate_t */
+static svn_tristate_t
+optbool_to_tristate(apr_uint64_t v)
+{
+ if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
+ return svn_tristate_true;
+ if (v == FALSE)
+ return svn_tristate_false;
+
+ return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
+}
+
+/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
+ server, which defaults to youngest. */
+static svn_error_t *ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ int i;
+ apr_array_header_t *mergeinfo_tuple;
+ svn_ra_svn_item_t *elt;
+ const char *path;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
+ svn_inheritance_to_word(inherit),
+ include_descendants));
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
+
+ *catalog = NULL;
+ if (mergeinfo_tuple->nelts > 0)
+ {
+ *catalog = apr_hash_make(pool);
+ for (i = 0; i < mergeinfo_tuple->nelts; i++)
+ {
+ svn_mergeinfo_t for_path;
+ const char *to_parse;
+
+ elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Mergeinfo element is not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
+ &path, &to_parse));
+ SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
+ /* Correct for naughty servers that send "relative" paths
+ with leading slashes! */
+ svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_update(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton, svn_revnum_t rev,
+ const char *target, svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start an update. */
+ SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
+ depth, send_copyfrom_args,
+ ignore_ancestry));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * update_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ra_svn_switch(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton, svn_revnum_t rev,
+ const char *target, svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = result_pool;
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start a switch. */
+ SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
+ switch_url, depth,
+ send_copyfrom_args, ignore_ancestry));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * update_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_status(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *target, svn_revnum_t rev,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start a status operation. */
+ SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
+ depth));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * status_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t rev, const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start a diff. */
+ SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
+ ignore_ancestry, versus_url,
+ text_deltas, depth));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * diff_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_pool_t *iterpool;
+ int i;
+ int nest_level = 0;
+ const char *path;
+ char *name;
+ svn_boolean_t want_custom_revprops;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
+ if (paths)
+ {
+ for (i = 0; i < paths->nelts; i++)
+ {
+ path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
+ }
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
+ discover_changed_paths, strict_node_history,
+ (apr_uint64_t) limit,
+ include_merged_revisions));
+ if (revprops)
+ {
+ want_custom_revprops = FALSE;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ name = APR_ARRAY_IDX(revprops, i, char *);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
+ if (!want_custom_revprops
+ && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
+ && strcmp(name, SVN_PROP_REVISION_DATE) != 0
+ && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
+ want_custom_revprops = TRUE;
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
+ want_custom_revprops = TRUE;
+ }
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Read the log messages. */
+ iterpool = svn_pool_create(pool);
+ while (1)
+ {
+ apr_uint64_t has_children_param, invalid_revnum_param;
+ apr_uint64_t has_subtractive_merge_param;
+ svn_string_t *author, *date, *message;
+ apr_array_header_t *cplist, *rplist;
+ svn_log_entry_t *log_entry;
+ svn_boolean_t has_children;
+ svn_boolean_t subtractive_merge = FALSE;
+ apr_uint64_t revprop_count;
+ svn_ra_svn_item_t *item;
+ apr_hash_t *cphash;
+ svn_revnum_t rev;
+ int nreceived;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ break;
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Log entry not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
+ "lr(?s)(?s)(?s)?BBnl?B",
+ &cplist, &rev, &author, &date,
+ &message, &has_children_param,
+ &invalid_revnum_param,
+ &revprop_count, &rplist,
+ &has_subtractive_merge_param));
+ if (want_custom_revprops && rplist == NULL)
+ {
+ /* Caller asked for custom revprops, but server is too old. */
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+ _("Server does not support custom revprops"
+ " via log"));
+ }
+
+ if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ has_children = FALSE;
+ else
+ has_children = (svn_boolean_t) has_children_param;
+
+ if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ subtractive_merge = FALSE;
+ else
+ subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
+
+ /* Because the svn protocol won't let us send an invalid revnum, we have
+ to recover that fact using the extra parameter. */
+ if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
+ && invalid_revnum_param)
+ rev = SVN_INVALID_REVNUM;
+
+ if (cplist->nelts > 0)
+ {
+ /* Interpret the changed-paths list. */
+ cphash = apr_hash_make(iterpool);
+ for (i = 0; i < cplist->nelts; i++)
+ {
+ svn_log_changed_path2_t *change;
+ const char *copy_path, *action, *cpath, *kind_str;
+ apr_uint64_t text_mods, prop_mods;
+ svn_revnum_t copy_rev;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
+ svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Changed-path entry not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool,
+ "cw(?cr)?(?c?BB)",
+ &cpath, &action, &copy_path,
+ &copy_rev, &kind_str,
+ &text_mods, &prop_mods));
+ cpath = svn_fspath__canonicalize(cpath, iterpool);
+ if (copy_path)
+ copy_path = svn_fspath__canonicalize(copy_path, iterpool);
+ change = svn_log_changed_path2_create(iterpool);
+ change->action = *action;
+ change->copyfrom_path = copy_path;
+ change->copyfrom_rev = copy_rev;
+ change->node_kind = svn_node_kind_from_word(kind_str);
+ change->text_modified = optbool_to_tristate(text_mods);
+ change->props_modified = optbool_to_tristate(prop_mods);
+ svn_hash_sets(cphash, cpath, change);
+ }
+ }
+ else
+ cphash = NULL;
+
+ nreceived = 0;
+ if (! (limit && (nest_level == 0) && (++nreceived > limit)))
+ {
+ log_entry = svn_log_entry_create(iterpool);
+
+ log_entry->changed_paths = cphash;
+ log_entry->changed_paths2 = cphash;
+ log_entry->revision = rev;
+ log_entry->has_children = has_children;
+ log_entry->subtractive_merge = subtractive_merge;
+ if (rplist)
+ SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
+ &log_entry->revprops));
+ if (log_entry->revprops == NULL)
+ log_entry->revprops = apr_hash_make(iterpool);
+ if (revprops == NULL)
+ {
+ /* Caller requested all revprops; set author/date/log. */
+ if (author)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+ author);
+ if (date)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
+ date);
+ if (message)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG,
+ message);
+ }
+ else
+ {
+ /* Caller requested some; maybe set author/date/log. */
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ name = APR_ARRAY_IDX(revprops, i, char *);
+ if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+ svn_hash_sets(log_entry->revprops,
+ SVN_PROP_REVISION_AUTHOR, author);
+ if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+ svn_hash_sets(log_entry->revprops,
+ SVN_PROP_REVISION_DATE, date);
+ if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+ svn_hash_sets(log_entry->revprops,
+ SVN_PROP_REVISION_LOG, message);
+ }
+ }
+ SVN_ERR(receiver(receiver_baton, log_entry, iterpool));
+ if (log_entry->has_children)
+ {
+ nest_level++;
+ }
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ SVN_ERR_ASSERT(nest_level);
+ nest_level--;
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Read the response. */
+ return svn_ra_svn__read_cmd_response(conn, pool, "");
+}
+
+
+static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
+ const char *path, svn_revnum_t rev,
+ svn_node_kind_t *kind, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ const char *kind_word;
+
+ SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
+ *kind = svn_node_kind_from_word(kind_word);
+ return SVN_NO_ERROR;
+}
+
+
+/* If ERR is a command not supported error, wrap it in a
+ SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
+static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
+ const char *msg)
+{
+ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
+ _(msg));
+ return err;
+}
+
+
+static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
+ const char *path, svn_revnum_t rev,
+ svn_dirent_t **dirent, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *list = NULL;
+ svn_dirent_t *the_dirent;
+
+ SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'stat' not implemented")));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
+
+ if (! list)
+ {
+ *dirent = NULL;
+ }
+ else
+ {
+ const char *kind, *cdate, *cauthor;
+ svn_boolean_t has_props;
+ svn_revnum_t crev;
+ apr_uint64_t size;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
+ &kind, &size, &has_props,
+ &crev, &cdate, &cauthor));
+
+ the_dirent = svn_dirent_create(pool);
+ the_dirent->kind = svn_node_kind_from_word(kind);
+ the_dirent->size = size;/* FIXME: svn_filesize_t */
+ the_dirent->has_props = has_props;
+ the_dirent->created_rev = crev;
+ SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
+ the_dirent->last_author = cauthor;
+
+ *dirent = the_dirent;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_revnum_t revision;
+ svn_boolean_t is_done;
+ int i;
+
+ /* Transmit the parameters. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
+ "get-locations", path, peg_revision));
+ for (i = 0; i < location_revisions->nelts; i++)
+ {
+ revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ /* Servers before 1.1 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-locations' not implemented")));
+
+ /* Read the hash items. */
+ is_done = FALSE;
+ *locations = apr_hash_make(pool);
+ while (!is_done)
+ {
+ svn_ra_svn_item_t *item;
+ const char *ret_path;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ is_done = 1;
+ else if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Location entry not a list"));
+ else
+ {
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
+ &revision, &ret_path));
+ ret_path = svn_fspath__canonicalize(ret_path, pool);
+ apr_hash_set(*locations, apr_pmemdup(pool, &revision,
+ sizeof(revision)),
+ sizeof(revision), ret_path);
+ }
+ }
+
+ /* Read the response. This is so the server would have a chance to
+ * report an error. */
+ return svn_ra_svn__read_cmd_response(conn, pool, "");
+}
+
+static svn_error_t *
+ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t is_done;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Transmit the parameters. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
+ "get-location-segments",
+ path, peg_revision, start_rev, end_rev));
+
+ /* Servers before 1.5 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-location-segments'"
+ " not implemented")));
+
+ /* Parse the response. */
+ is_done = FALSE;
+ while (!is_done)
+ {
+ svn_revnum_t range_start, range_end;
+ svn_ra_svn_item_t *item;
+ const char *ret_path;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ is_done = 1;
+ else if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Location segment entry not a list"));
+ else
+ {
+ svn_location_segment_t *segment = apr_pcalloc(iterpool,
+ sizeof(*segment));
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
+ &range_start, &range_end, &ret_path));
+ if (! (SVN_IS_VALID_REVNUM(range_start)
+ && SVN_IS_VALID_REVNUM(range_end)))
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Expected valid revision range"));
+ if (ret_path)
+ ret_path = svn_relpath_canonicalize(ret_path, iterpool);
+ segment->path = ret_path;
+ segment->range_start = range_start;
+ segment->range_end = range_end;
+ SVN_ERR(receiver(segment, receiver_baton, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Read the response. This is so the server would have a chance to
+ * report an error. */
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ apr_pool_t *rev_pool, *chunk_pool;
+ svn_boolean_t has_txdelta;
+ svn_boolean_t had_revision = FALSE;
+
+ /* One sub-pool for each revision and one for each txdelta chunk.
+ Note that the rev_pool must live during the following txdelta. */
+ rev_pool = svn_pool_create(pool);
+ chunk_pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
+ path, start, end,
+ include_merged_revisions));
+
+ /* Servers before 1.1 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-file-revs' not implemented")));
+
+ while (1)
+ {
+ apr_array_header_t *rev_proplist, *proplist;
+ apr_uint64_t merged_rev_param;
+ apr_array_header_t *props;
+ svn_ra_svn_item_t *item;
+ apr_hash_t *rev_props;
+ svn_revnum_t rev;
+ const char *p;
+ svn_boolean_t merged_rev;
+ svn_txdelta_window_handler_t d_handler;
+ void *d_baton;
+
+ svn_pool_clear(rev_pool);
+ svn_pool_clear(chunk_pool);
+ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ break;
+ /* Either we've got a correct revision or we will error out below. */
+ had_revision = TRUE;
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Revision entry not a list"));
+
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
+ "crll?B", &p, &rev, &rev_proplist,
+ &proplist, &merged_rev_param));
+ p = svn_fspath__canonicalize(p, rev_pool);
+ SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
+ SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
+ if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ merged_rev = FALSE;
+ else
+ merged_rev = (svn_boolean_t) merged_rev_param;
+
+ /* Get the first delta chunk so we know if there is a delta. */
+ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Text delta chunk not a string"));
+ has_txdelta = item->u.string->len > 0;
+
+ SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
+ has_txdelta ? &d_handler : NULL, &d_baton,
+ props, rev_pool));
+
+ /* Process the text delta if any. */
+ if (has_txdelta)
+ {
+ svn_stream_t *stream;
+
+ if (d_handler)
+ stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
+ rev_pool);
+ else
+ stream = NULL;
+ while (item->u.string->len > 0)
+ {
+ apr_size_t size;
+
+ size = item->u.string->len;
+ if (stream)
+ SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
+ svn_pool_clear(chunk_pool);
+
+ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
+ &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Text delta chunk not a string"));
+ }
+ if (stream)
+ SVN_ERR(svn_stream_close(stream));
+ }
+ }
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
+
+ /* Return error if we didn't get any revisions. */
+ if (!had_revision)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("The get-file-revs command didn't return "
+ "any revisions"));
+
+ svn_pool_destroy(chunk_pool);
+ svn_pool_destroy(rev_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* For each path in PATH_REVS, send a 'lock' command to the server.
+ Used with 1.2.x series servers which support locking, but of only
+ one path at a time. ra_svn_lock(), which supports 'lock-many'
+ is now the default. See svn_ra_lock() docstring for interface details. */
+static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_array_header_t *list;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(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;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ revnum = val;
+
+ SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
+ steal_lock, *revnum));
+
+ /* Servers before 1.2 doesn't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support "
+ "the lock command")));
+
+ err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
+
+ if (!err)
+ SVN_ERR(parse_lock(list, iterpool, &lock));
+
+ 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;
+}
+
+/* For each path in PATH_TOKENS, send an 'unlock' command to the server.
+ Used with 1.2.x series servers which support unlocking, but of only
+ one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
+ now the default. See svn_ra_unlock() docstring for interface details. */
+static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t break_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(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 *token;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ if (strcmp(val, "") != 0)
+ token = val;
+ else
+ token = NULL;
+
+ SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
+ break_lock));
+
+ /* Servers before 1.2 don't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
+ N_("Server doesn't support the unlock "
+ "command")));
+
+ err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
+
+ if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Tell the server to lock all paths in PATH_REVS.
+ See svn_ra_lock() for interface details. */
+static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t *conn = sess->conn;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
+ comment, steal_lock));
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path;
+ void *val;
+ svn_revnum_t *revnum;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ revnum = val;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ err = handle_auth_request(sess, pool);
+
+ /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
+ * to 'lock'. */
+ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ {
+ svn_error_clear(err);
+ return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
+ lock_func, lock_baton, pool);
+ }
+
+ if (err)
+ return err;
+
+ /* Loop over responses to get lock information. */
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ svn_ra_svn_item_t *elt;
+ const void *key;
+ const char *path;
+ svn_error_t *callback_err;
+ const char *status;
+ svn_lock_t *lock;
+ apr_array_header_t *list;
+
+ apr_hash_this(hi, &key, NULL, NULL);
+ path = key;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
+
+ /* The server might have encountered some sort of fatal error in
+ the middle of the request list. If this happens, it will
+ transmit "done" to end the lock-info early, and then the
+ overall command response will talk about the fatal error. */
+ if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
+ break;
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Lock response not a list"));
+
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
+ &list));
+
+ if (strcmp(status, "failure") == 0)
+ err = svn_ra_svn__handle_failure_status(list, iterpool);
+ else if (strcmp(status, "success") == 0)
+ {
+ SVN_ERR(parse_lock(list, iterpool, &lock));
+ err = NULL;
+ }
+ else
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown status for lock command"));
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, TRUE,
+ err ? NULL : lock,
+ err, iterpool);
+ else
+ callback_err = SVN_NO_ERROR;
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ /* If we didn't break early above, and the whole hash was traversed,
+ read the final "done" from the server. */
+ if (!hi)
+ {
+ svn_ra_svn_item_t *elt;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
+ if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Didn't receive end marker for lock "
+ "responses"));
+ }
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Tell the server to unlock all paths in PATH_TOKENS.
+ See svn_ra_unlock() for interface details. */
+static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t break_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t *conn = sess->conn;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_error_t *err;
+ const char *path;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
+ break_lock));
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ const void *key;
+ const char *token;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+
+ if (strcmp(val, "") != 0)
+ token = val;
+ else
+ token = NULL;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ err = handle_auth_request(sess, pool);
+
+ /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
+ * to 'unlock'.
+ */
+ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ {
+ svn_error_clear(err);
+ return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
+ lock_baton, pool);
+ }
+
+ if (err)
+ return err;
+
+ /* Loop over responses to unlock files. */
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ svn_ra_svn_item_t *elt;
+ const void *key;
+ svn_error_t *callback_err;
+ const char *status;
+ apr_array_header_t *list;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
+
+ /* The server might have encountered some sort of fatal error in
+ the middle of the request list. If this happens, it will
+ transmit "done" to end the lock-info early, and then the
+ overall command response will talk about the fatal error. */
+ if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
+ break;
+
+ apr_hash_this(hi, &key, NULL, NULL);
+ path = key;
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unlock response not a list"));
+
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
+ &list));
+
+ if (strcmp(status, "failure") == 0)
+ err = svn_ra_svn__handle_failure_status(list, iterpool);
+ else if (strcmp(status, "success") == 0)
+ {
+ SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
+ err = SVN_NO_ERROR;
+ }
+ else
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown status for unlock command"));
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
+ iterpool);
+ else
+ callback_err = SVN_NO_ERROR;
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ /* If we didn't break early above, and the whole hash was traversed,
+ read the final "done" from the server. */
+ if (!hi)
+ {
+ svn_ra_svn_item_t *elt;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
+ if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Didn't receive end marker for unlock "
+ "responses"));
+ }
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_array_header_t *list;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
+
+ /* Servers before 1.2 doesn't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the get-lock "
+ "command")));
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
+ if (list)
+ SVN_ERR(parse_lock(list, pool, lock));
+ else
+ *lock = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
+ to prevent a dependency cycle. */
+static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
+ const char **rel_path,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *root_url;
+
+ SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
+ *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
+ if (! *rel_path)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s' isn't a child of repository root "
+ "URL '%s'"),
+ url, root_url);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_array_header_t *list;
+ const char *full_url, *abs_path;
+ int i;
+
+ /* Figure out the repository abspath from PATH. */
+ full_url = svn_path_url_add_component2(sess->url, path, pool);
+ SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
+ abs_path = svn_fspath__canonicalize(abs_path, pool);
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
+
+ /* Servers before 1.2 doesn't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the get-lock "
+ "command")));
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
+
+ *locks = apr_hash_make(pool);
+
+ for (i = 0; i < list->nelts; ++i)
+ {
+ svn_lock_t *lock;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Lock element not a list"));
+ SVN_ERR(parse_lock(elt->u.list, pool, &lock));
+
+ /* Filter out unwanted paths. Since Subversion only allows
+ locks on files, we can treat depth=immediates the same as
+ depth=files for filtering purposes. Meaning, we'll keep
+ this lock if:
+
+ a) its path is the very path we queried, or
+ b) we've asked for a fully recursive answer, or
+ c) we've asked for depth=files or depth=immediates, and this
+ lock is on an immediate child of our query path.
+ */
+ if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
+ {
+ svn_hash_sets(*locks, lock->path, lock);
+ }
+ else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
+ {
+ const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
+ if (relpath && (svn_path_component_count(relpath) == 1))
+ svn_hash_sets(*locks, lock->path, lock);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *ra_svn_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_svn__session_baton_t *sess = session->priv;
+
+ SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
+ low_water_mark, send_deltas));
+
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the replay "
+ "command")));
+
+ SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
+ NULL, TRUE));
+
+ return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
+}
+
+
+static svn_error_t *
+ra_svn_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)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ apr_pool_t *iterpool;
+ svn_revnum_t rev;
+ svn_boolean_t drive_aborted = FALSE;
+
+ SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
+ start_revision, end_revision,
+ low_water_mark, send_deltas));
+
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the "
+ "replay-range command")));
+
+ iterpool = svn_pool_create(pool);
+ for (rev = start_revision; rev <= end_revision; rev++)
+ {
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *rev_props;
+ const char *word;
+ apr_array_header_t *list;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
+ "wl", &word, &list));
+ if (strcmp(word, "revprops") != 0)
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Expected 'revprops', found '%s'"),
+ word);
+
+ SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
+
+ SVN_ERR(revstart_func(rev, replay_baton,
+ &editor, &edit_baton,
+ rev_props,
+ iterpool));
+ SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
+ editor, edit_baton,
+ &drive_aborted, TRUE));
+ /* If drive_editor2() aborted the commit, do NOT try to call
+ revfinish_func and commit the transaction! */
+ if (drive_aborted) {
+ svn_pool_destroy(iterpool);
+ return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
+ _("Error while replaying commit"));
+ }
+ SVN_ERR(revfinish_func(rev, replay_baton,
+ editor, edit_baton,
+ rev_props,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
+}
+
+
+static svn_error_t *
+ra_svn_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ static const char* capabilities[][2] =
+ {
+ /* { ra capability string, svn:// wire capability string} */
+ {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
+ {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
+ {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
+ {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
+ {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
+ {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
+ {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
+ {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
+ {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
+ SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
+
+ {NULL, NULL} /* End of list marker */
+ };
+ int i;
+
+ *has = FALSE;
+
+ for (i = 0; capabilities[i][0]; i++)
+ {
+ if (strcmp(capability, capabilities[i][0]) == 0)
+ {
+ *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"),
+ capability);
+}
+
+static svn_error_t *
+ra_svn_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_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ /* Transmit the parameters. */
+ SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
+ peg_revision, end_revision));
+
+ /* Servers before 1.6 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-deleted-rev' not implemented")));
+
+ return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted);
+}
+
+static svn_error_t *
+ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ conn->shim_callbacks = callbacks;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ra_svn_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *iproplist;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
+ path, revision));
+ SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
+ SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra__vtable_t ra_svn_vtable = {
+ svn_ra_svn_version,
+ ra_svn_get_description,
+ ra_svn_get_schemes,
+ ra_svn_open,
+ ra_svn_reparent,
+ ra_svn_get_session_url,
+ ra_svn_get_latest_rev,
+ ra_svn_get_dated_rev,
+ ra_svn_change_rev_prop,
+ ra_svn_rev_proplist,
+ ra_svn_rev_prop,
+ ra_svn_commit,
+ ra_svn_get_file,
+ ra_svn_get_dir,
+ ra_svn_get_mergeinfo,
+ ra_svn_update,
+ ra_svn_switch,
+ ra_svn_status,
+ ra_svn_diff,
+ ra_svn_log,
+ ra_svn_check_path,
+ ra_svn_stat,
+ ra_svn_get_uuid,
+ ra_svn_get_repos_root,
+ ra_svn_get_locations,
+ ra_svn_get_location_segments,
+ ra_svn_get_file_revs,
+ ra_svn_lock,
+ ra_svn_unlock,
+ ra_svn_get_lock,
+ ra_svn_get_locks,
+ ra_svn_replay,
+ ra_svn_has_capability,
+ ra_svn_replay_range,
+ ra_svn_get_deleted_rev,
+ ra_svn_register_editor_shim_callbacks,
+ ra_svn_get_inherited_props
+};
+
+svn_error_t *
+svn_ra_svn__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 },
+ { NULL, NULL }
+ };
+
+ SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist));
+
+ /* 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_svn"),
+ loader_version->major);
+ }
+
+ *vtable = &ra_svn_vtable;
+
+#ifdef SVN_HAVE_SASL
+ SVN_ERR(svn_ra_svn__sasl_init());
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for the 1.1 and before API. */
+#define NAME "ra_svn"
+#define DESCRIPTION RA_SVN_DESCRIPTION
+#define VTBL ra_svn_vtable
+#define INITFUNC svn_ra_svn__init
+#define COMPAT_INITFUNC svn_ra_svn_init
+#include "../libsvn_ra/wrapper_template.h"
OpenPOWER on IntegriCloud