2 * client.c : Functions for repository access via the Subversion protocol
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #include "svn_private_config.h"
28 #define APR_WANT_STRFUNC
30 #include <apr_general.h>
31 #include <apr_strings.h>
32 #include <apr_network_io.h>
36 #include "svn_types.h"
37 #include "svn_string.h"
38 #include "svn_dirent_uri.h"
39 #include "svn_error.h"
42 #include "svn_pools.h"
43 #include "svn_config.h"
45 #include "svn_ra_svn.h"
46 #include "svn_props.h"
47 #include "svn_mergeinfo.h"
48 #include "svn_version.h"
50 #include "svn_private_config.h"
52 #include "private/svn_fspath.h"
54 #include "../libsvn_ra/ra_loader.h"
59 #define DO_AUTH svn_ra_svn__do_cyrus_auth
61 #define DO_AUTH svn_ra_svn__do_internal_auth
64 /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
65 whatever reason) deems svn_depth_immediates as non-recursive, which
66 is ... kinda true, but not true enough for our purposes. We need
67 our requested recursion level to be *at least* as recursive as the
68 real depth we're looking for.
70 #define DEPTH_TO_RECURSE(d) \
71 ((d) == svn_depth_unknown || (d) > svn_depth_files)
73 typedef struct ra_svn_commit_callback_baton_t {
74 svn_ra_svn__session_baton_t *sess_baton;
76 svn_revnum_t *new_rev;
77 svn_commit_callback2_t callback;
79 } ra_svn_commit_callback_baton_t;
81 typedef struct ra_svn_reporter_baton_t {
82 svn_ra_svn__session_baton_t *sess_baton;
83 svn_ra_svn_conn_t *conn;
85 const svn_delta_editor_t *editor;
87 } ra_svn_reporter_baton_t;
89 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
91 static void parse_tunnel(const char *url, const char **tunnel,
96 if (strncasecmp(url, "svn", 3) != 0)
100 /* Get the tunnel specification, if any. */
106 p = strchr(url, ':');
109 *tunnel = apr_pstrmemdup(pool, url, p - url);
113 static svn_error_t *make_connection(const char *hostname, unsigned short port,
114 apr_socket_t **sock, apr_pool_t *pool)
118 int family = APR_INET;
120 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
121 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
122 create IPV6 sockets. */
125 #ifdef MAX_SECS_TO_LINGER
126 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
128 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
129 APR_PROTO_TCP, pool);
133 apr_socket_close(*sock);
138 /* Resolve the hostname. */
139 status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
141 return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
143 /* Iterate through the returned list of addresses attempting to
144 * connect to each in turn. */
147 /* Create the socket. */
148 #ifdef MAX_SECS_TO_LINGER
149 /* ### old APR interface */
150 status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
152 status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
155 if (status == APR_SUCCESS)
157 status = apr_socket_connect(*sock, sa);
158 if (status != APR_SUCCESS)
159 apr_socket_close(*sock);
163 while (status != APR_SUCCESS && sa);
166 return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
169 /* Enable TCP keep-alives on the socket so we time out when
170 * the connection breaks due to network-layer problems.
171 * If the peer has dropped the connection due to a network partition
172 * or a crash, or if the peer no longer considers the connection
173 * valid because we are behind a NAT and our public IP has changed,
174 * it will respond to the keep-alive probe with a RST instead of an
175 * acknowledgment segment, which will cause svn to abort the session
176 * even while it is currently blocked waiting for data from the peer.
177 * See issue #3347. */
178 status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
181 /* It's not a fatal error if we cannot enable keep-alives. */
187 /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
188 property diffs in LIST, received from the server. */
189 static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
191 apr_array_header_t **diffs)
195 *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
197 for (i = 0; i < list->nelts; i++)
200 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
202 if (elt->kind != SVN_RA_SVN_LIST)
203 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
204 _("Prop diffs element not a list"));
205 prop = apr_array_push(*diffs);
206 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
212 /* Parse a lockdesc, provided in LIST as specified by the protocol into
213 LOCK, allocated in POOL. */
214 static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
217 const char *cdate, *edate;
218 *lock = svn_lock_create(pool);
219 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
220 &(*lock)->token, &(*lock)->owner,
221 &(*lock)->comment, &cdate, &edate));
222 (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
223 SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
225 SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
229 /* --- AUTHENTICATION ROUTINES --- */
231 svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
233 const char *mech, const char *mech_arg)
235 return svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg);
238 static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
241 svn_ra_svn_conn_t *conn = sess->conn;
242 apr_array_header_t *mechlist;
245 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
246 if (mechlist->nelts == 0)
248 return DO_AUTH(sess, mechlist, realm, pool);
251 /* --- REPORTER IMPLEMENTATION --- */
253 static svn_error_t *ra_svn_set_path(void *baton, const char *path,
256 svn_boolean_t start_empty,
257 const char *lock_token,
260 ra_svn_reporter_baton_t *b = baton;
262 SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
263 start_empty, lock_token, depth));
267 static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
270 ra_svn_reporter_baton_t *b = baton;
272 SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
276 static svn_error_t *ra_svn_link_path(void *baton, const char *path,
280 svn_boolean_t start_empty,
281 const char *lock_token,
284 ra_svn_reporter_baton_t *b = baton;
286 SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
287 start_empty, lock_token, depth));
291 static svn_error_t *ra_svn_finish_report(void *baton,
294 ra_svn_reporter_baton_t *b = baton;
296 SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
297 SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
298 SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
300 SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
304 static svn_error_t *ra_svn_abort_report(void *baton,
307 ra_svn_reporter_baton_t *b = baton;
309 SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
313 static svn_ra_reporter3_t ra_svn_reporter = {
317 ra_svn_finish_report,
321 /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
322 * EDITOR/EDIT_BATON when it gets the finish_report() call.
324 * Allocate the new reporter in POOL.
327 ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
329 const svn_delta_editor_t *editor,
333 const svn_ra_reporter3_t **reporter,
336 ra_svn_reporter_baton_t *b;
337 const svn_delta_editor_t *filter_editor;
340 /* We can skip the depth filtering when the user requested
341 depth_files or depth_infinity because the server will
342 transmit the right stuff anyway. */
343 if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
344 && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
346 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
348 editor, edit_baton, depth,
351 editor = filter_editor;
352 edit_baton = filter_baton;
355 b = apr_palloc(pool, sizeof(*b));
356 b->sess_baton = sess_baton;
357 b->conn = sess_baton->conn;
360 b->edit_baton = edit_baton;
362 *reporter = &ra_svn_reporter;
368 /* --- RA LAYER IMPLEMENTATION --- */
370 /* (Note: *ARGV is an output parameter.) */
371 static svn_error_t *find_tunnel_agent(const char *tunnel,
372 const char *hostinfo,
374 apr_hash_t *config, apr_pool_t *pool)
377 const char *val, *var, *cmd;
383 /* Look up the tunnel specification in config. */
384 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
385 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
387 /* We have one predefined tunnel scheme, if it isn't overridden by config. */
388 if (!val && strcmp(tunnel, "ssh") == 0)
390 /* Killing the tunnel agent with SIGTERM leads to unsightly
391 * stderr output from ssh, unless we pass -q.
392 * The "-q" option to ssh is widely supported: all versions of
393 * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
394 * versions have it too. If the user is using some other ssh
395 * implementation that doesn't accept it, they can override it
396 * in the [tunnels] section of the config. */
397 val = "$SVN_SSH ssh -q";
401 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
402 _("Undefined tunnel scheme '%s'"), tunnel);
404 /* If the scheme definition begins with "$varname", it means there
405 * is an environment variable which can override the command. */
409 len = strcspn(val, " ");
410 var = apr_pstrmemdup(pool, val, len);
418 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
419 _("Tunnel scheme %s requires environment "
420 "variable %s to be defined"), tunnel,
427 /* Tokenize the command into a list of arguments. */
428 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
429 if (status != APR_SUCCESS)
430 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
432 /* Append the fixed arguments to the result. */
433 for (n = 0; cmd_argv[n] != NULL; n++)
435 *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
436 memcpy((void *) *argv, cmd_argv, n * sizeof(char *));
437 (*argv)[n++] = svn_path_uri_decode(hostinfo, pool);
438 (*argv)[n++] = "svnserve";
445 /* This function handles any errors which occur in the child process
446 * created for a tunnel agent. We write the error out as a command
447 * failure; the code in ra_svn_open() to read the server's greeting
448 * will see the error and return it to the caller. */
449 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
452 svn_ra_svn_conn_t *conn;
453 apr_file_t *in_file, *out_file;
456 if (apr_file_open_stdin(&in_file, pool)
457 || apr_file_open_stdout(&out_file, pool))
460 conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
461 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
463 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
464 svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
465 svn_error_clear(err);
466 svn_error_clear(svn_ra_svn__flush(conn, pool));
469 /* (Note: *CONN is an output parameter.) */
470 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
475 apr_procattr_t *attr;
478 status = apr_procattr_create(&attr, pool);
479 if (status == APR_SUCCESS)
480 status = apr_procattr_io_set(attr, 1, 1, 0);
481 if (status == APR_SUCCESS)
482 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
483 if (status == APR_SUCCESS)
484 status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
485 proc = apr_palloc(pool, sizeof(*proc));
486 if (status == APR_SUCCESS)
487 status = apr_proc_create(proc, *args, args, NULL, attr, pool);
488 if (status != APR_SUCCESS)
489 return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
490 svn_error_wrap_apr(status,
491 _("Can't create tunnel")), NULL);
493 /* Arrange for the tunnel agent to get a SIGTERM on pool
494 * cleanup. This is a little extreme, but the alternatives
495 * weren't working out.
497 * Closing the pipes and waiting for the process to die
498 * was prone to mysterious hangs which are difficult to
499 * diagnose (e.g. svnserve dumps core due to unrelated bug;
500 * sshd goes into zombie state; ssh connection is never
501 * closed; ssh never terminates).
502 * See also the long dicussion in issue #2580 if you really
503 * want to know various reasons for these problems and
504 * the different opinions on this issue.
506 * On Win32, APR does not support KILL_ONLY_ONCE. It only has
507 * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
508 * KILL_ALWAYS, which immediately calls TerminateProcess().
509 * This instantly kills the tunnel, leaving sshd and svnserve
510 * on a remote machine running indefinitely. These processes
511 * accumulate. The problem is most often seen with a fast client
512 * machine and a modest internet connection, as the tunnel
513 * is killed before being able to gracefully complete the
514 * session. In that case, svn is unusable 100% of the time on
515 * the windows machine. Thus, on Win32, we use KILL_NEVER and
516 * take the lesser of two evils.
519 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
521 apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
524 /* APR pipe objects inherit by default. But we don't want the
525 * tunnel agent's pipes held open by future child processes
526 * (such as other ra_svn sessions), so turn that off. */
527 apr_file_inherit_unset(proc->in);
528 apr_file_inherit_unset(proc->out);
530 /* Guard against dotfile output to stdout on the server. */
531 *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in,
532 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
534 err = svn_ra_svn__skip_leading_garbage(*conn, pool);
536 return svn_error_quick_wrap(
538 _("To better debug SSH connection problems, remove the -q "
539 "option from 'ssh' in the [tunnels] section of your "
540 "Subversion configuration file."));
545 /* Parse URL inot URI, validating it and setting the default port if none
546 was given. Allocate the URI fileds out of POOL. */
547 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
550 apr_status_t apr_err;
552 apr_err = apr_uri_parse(pool, url, uri);
555 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
556 _("Illegal svn repository URL '%s'"), url);
559 uri->port = SVN_RA_SVN_PORT;
564 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
565 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
566 are provided by the caller of ra_svn_open. If tunnel_argv is non-null,
567 it points to a program argument list to use when invoking the tunnel agent.
569 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
571 const apr_uri_t *uri,
572 const char **tunnel_argv,
573 const svn_ra_callbacks2_t *callbacks,
574 void *callbacks_baton,
577 svn_ra_svn__session_baton_t *sess;
578 svn_ra_svn_conn_t *conn;
580 apr_uint64_t minver, maxver;
581 apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
582 const char *client_string = NULL;
584 sess = apr_palloc(pool, sizeof(*sess));
586 sess->is_tunneled = (tunnel_argv != NULL);
587 sess->url = apr_pstrdup(pool, url);
588 sess->user = uri->user;
589 sess->hostname = uri->hostname;
590 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
592 sess->tunnel_argv = tunnel_argv;
593 sess->callbacks = callbacks;
594 sess->callbacks_baton = callbacks_baton;
595 sess->bytes_read = sess->bytes_written = 0;
598 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
601 SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
602 conn = svn_ra_svn_create_conn3(sock, NULL, NULL,
603 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
607 /* Build the useragent string, querying the client for any
608 customizations it wishes to note. For historical reasons, we
609 still deliver the hard-coded client version info
610 (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
611 separately in the protocol/capabilities handshake below. But the
612 commit logic wants the combined form for use with the
613 SVN_PROP_TXN_USER_AGENT ephemeral property because that's
614 consistent with our DAV approach. */
615 if (sess->callbacks->get_client_string != NULL)
616 SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
617 &client_string, pool));
619 sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
620 client_string, (char *)NULL);
622 sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
624 /* Make sure we set conn->session before reading from it,
625 * because the reader and writer functions expect a non-NULL value. */
627 conn->session = sess;
629 /* Read server's greeting. */
630 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
631 &mechlist, &server_caplist));
633 /* We support protocol version 2. */
635 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
636 _("Server requires minimum version %d"),
639 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
640 _("Server only supports versions up to %d"),
642 SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
644 /* All released versions of Subversion support edit-pipeline,
645 * so we do not support servers that do not. */
646 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
647 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
648 _("Server does not support edit pipelining"));
650 /* In protocol version 2, we send back our protocol version, our
651 * capability list, and the URL, and subsequently there is an auth
653 /* Client-side capabilities list: */
654 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
656 SVN_RA_SVN_CAP_EDIT_PIPELINE,
657 SVN_RA_SVN_CAP_SVNDIFF1,
658 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
659 SVN_RA_SVN_CAP_DEPTH,
660 SVN_RA_SVN_CAP_MERGEINFO,
661 SVN_RA_SVN_CAP_LOG_REVPROPS,
663 SVN_RA_SVN__DEFAULT_USERAGENT,
665 SVN_ERR(handle_auth_request(sess, pool));
667 /* This is where the security layer would go into effect if we
668 * supported security layers, which is a ways off. */
670 /* Read the repository's uuid and root URL, and perhaps learn more
671 capabilities that weren't available before now. */
672 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
673 &conn->repos_root, &repos_caplist));
675 SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
677 if (conn->repos_root)
679 conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
680 /* We should check that the returned string is a prefix of url, since
681 that's the API guarantee, but this isn't true for 1.0 servers.
682 Checking the length prevents client crashes. */
683 if (strlen(conn->repos_root) > strlen(url))
684 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
685 _("Impossibly long repository root from "
696 #define RA_SVN_DESCRIPTION \
697 N_("Module for accessing a repository using the svn network protocol.\n" \
698 " - with Cyrus SASL authentication")
700 #define RA_SVN_DESCRIPTION \
701 N_("Module for accessing a repository using the svn network protocol.")
704 static const char *ra_svn_get_description(void)
706 return _(RA_SVN_DESCRIPTION);
709 static const char * const *
710 ra_svn_get_schemes(apr_pool_t *pool)
712 static const char *schemes[] = { "svn", NULL };
719 static svn_error_t *ra_svn_open(svn_ra_session_t *session,
720 const char **corrected_url,
722 const svn_ra_callbacks2_t *callbacks,
723 void *callback_baton,
727 apr_pool_t *sess_pool = svn_pool_create(pool);
728 svn_ra_svn__session_baton_t *sess;
729 const char *tunnel, **tunnel_argv;
731 svn_config_t *cfg, *cfg_client;
733 /* We don't support server-prescribed redirections in ra-svn. */
735 *corrected_url = NULL;
737 SVN_ERR(parse_url(url, &uri, sess_pool));
739 parse_tunnel(url, &tunnel, pool);
742 SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
748 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
750 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
751 svn_auth_set_parameter(callbacks->auth_baton,
752 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
753 svn_auth_set_parameter(callbacks->auth_baton,
754 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
756 /* We open the session in a subpool so we can get rid of it if we
757 reparent with a server that doesn't support reparenting. */
758 SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
759 callbacks, callback_baton, sess_pool));
760 session->priv = sess;
765 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
769 svn_ra_svn__session_baton_t *sess = ra_session->priv;
770 svn_ra_svn_conn_t *conn = sess->conn;
772 apr_pool_t *sess_pool;
773 svn_ra_svn__session_baton_t *new_sess;
776 SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
777 err = handle_auth_request(sess, pool);
780 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
781 sess->url = apr_pstrdup(sess->pool, url);
784 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
787 /* Servers before 1.4 doesn't support this command; try to reconnect
789 svn_error_clear(err);
790 /* Create a new subpool of the RA session pool. */
791 sess_pool = svn_pool_create(ra_session->pool);
792 err = parse_url(url, &uri, sess_pool);
794 err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
795 sess->callbacks, sess->callbacks_baton, sess_pool);
796 /* We destroy the new session pool on error, since it is allocated in
797 the main session pool. */
800 svn_pool_destroy(sess_pool);
804 /* We have a new connection, assign it and destroy the old. */
805 ra_session->priv = new_sess;
806 svn_pool_destroy(sess->pool);
811 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
812 const char **url, apr_pool_t *pool)
814 svn_ra_svn__session_baton_t *sess = session->priv;
815 *url = apr_pstrdup(pool, sess->url);
819 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
820 svn_revnum_t *rev, apr_pool_t *pool)
822 svn_ra_svn__session_baton_t *sess_baton = session->priv;
823 svn_ra_svn_conn_t *conn = sess_baton->conn;
825 SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
826 SVN_ERR(handle_auth_request(sess_baton, pool));
827 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
831 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
832 svn_revnum_t *rev, apr_time_t tm,
835 svn_ra_svn__session_baton_t *sess_baton = session->priv;
836 svn_ra_svn_conn_t *conn = sess_baton->conn;
838 SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
839 SVN_ERR(handle_auth_request(sess_baton, pool));
840 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
844 /* Forward declaration. */
845 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
847 const char *capability,
850 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
852 const svn_string_t *const *old_value_p,
853 const svn_string_t *value,
856 svn_ra_svn__session_baton_t *sess_baton = session->priv;
857 svn_ra_svn_conn_t *conn = sess_baton->conn;
858 svn_boolean_t dont_care;
859 const svn_string_t *old_value;
860 svn_boolean_t has_atomic_revprops;
862 SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
863 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
868 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
869 SVN_ERR_ASSERT(has_atomic_revprops);
872 old_value = *old_value_p;
880 if (has_atomic_revprops)
881 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
885 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
888 SVN_ERR(handle_auth_request(sess_baton, pool));
889 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
893 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
896 svn_ra_svn__session_baton_t *sess_baton = session->priv;
897 svn_ra_svn_conn_t *conn = sess_baton->conn;
903 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
906 svn_ra_svn__session_baton_t *sess_baton = session->priv;
907 svn_ra_svn_conn_t *conn = sess_baton->conn;
909 if (!conn->repos_root)
910 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
911 _("Server did not send repository root"));
912 *url = conn->repos_root;
916 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
917 apr_hash_t **props, apr_pool_t *pool)
919 svn_ra_svn__session_baton_t *sess_baton = session->priv;
920 svn_ra_svn_conn_t *conn = sess_baton->conn;
921 apr_array_header_t *proplist;
923 SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
924 SVN_ERR(handle_auth_request(sess_baton, pool));
925 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
926 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
930 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
932 svn_string_t **value, apr_pool_t *pool)
934 svn_ra_svn__session_baton_t *sess_baton = session->priv;
935 svn_ra_svn_conn_t *conn = sess_baton->conn;
937 SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
938 SVN_ERR(handle_auth_request(sess_baton, pool));
939 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
943 static svn_error_t *ra_svn_end_commit(void *baton)
945 ra_svn_commit_callback_baton_t *ccb = baton;
946 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
948 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
949 SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
951 &(commit_info->revision),
952 &(commit_info->date),
953 &(commit_info->author),
954 &(commit_info->post_commit_err)));
957 SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
962 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
963 const svn_delta_editor_t **editor,
965 apr_hash_t *revprop_table,
966 svn_commit_callback2_t callback,
967 void *callback_baton,
968 apr_hash_t *lock_tokens,
969 svn_boolean_t keep_locks,
972 svn_ra_svn__session_baton_t *sess_baton = session->priv;
973 svn_ra_svn_conn_t *conn = sess_baton->conn;
974 ra_svn_commit_callback_baton_t *ccb;
975 apr_hash_index_t *hi;
976 apr_pool_t *iterpool;
977 const svn_string_t *log_msg = svn_hash_gets(revprop_table,
978 SVN_PROP_REVISION_LOG);
980 if (log_msg == NULL &&
981 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
983 return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
984 _("ra_svn does not support not specifying "
985 "a log message with pre-1.5 servers; "
986 "consider passing an empty one, or upgrading "
989 else if (log_msg == NULL)
990 /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument
991 to the 'commit' protocol command is non-optional; on the server side,
992 only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The
993 "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
994 will have a NULL log message (not just "", really NULL).
996 svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
997 present; this was elevated to a protocol promise in r1498550 (and
998 later documented in this comment) in order to fix the segmentation
999 fault bug described in the log message of r1498550.*/
1000 log_msg = svn_string_create("", pool);
1002 /* If we're sending revprops other than svn:log, make sure the server won't
1003 silently ignore them. */
1004 if (apr_hash_count(revprop_table) > 1 &&
1005 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1006 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1007 _("Server doesn't support setting arbitrary "
1008 "revision properties during commit"));
1010 /* If the server supports ephemeral txnprops, add the one that
1011 reports the client's version level string. */
1012 if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1013 svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1015 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1016 svn_string_create(SVN_VER_NUMBER, pool));
1017 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1018 svn_string_create(sess_baton->useragent, pool));
1021 /* Tell the server we're starting the commit.
1022 Send log message here for backwards compatibility with servers
1024 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1028 iterpool = svn_pool_create(pool);
1029 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1033 const char *path, *token;
1035 svn_pool_clear(iterpool);
1036 apr_hash_this(hi, &key, NULL, &val);
1039 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1041 svn_pool_destroy(iterpool);
1043 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1044 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1045 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1046 SVN_ERR(handle_auth_request(sess_baton, pool));
1047 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1049 /* Remember a few arguments for when the commit is over. */
1050 ccb = apr_palloc(pool, sizeof(*ccb));
1051 ccb->sess_baton = sess_baton;
1053 ccb->new_rev = NULL;
1054 ccb->callback = callback;
1055 ccb->callback_baton = callback_baton;
1057 /* Fetch an editor for the caller to drive. The editor will call
1058 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1059 * in the new_rev, committed_date, and committed_author values. */
1060 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1061 ra_svn_end_commit, ccb);
1062 return SVN_NO_ERROR;
1065 /* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1066 const char * repos relative paths and properties for those paths, storing
1067 the result as an array of svn_prop_inherited_item_t *items. */
1068 static svn_error_t *
1069 parse_iproplist(apr_array_header_t **inherited_props,
1070 const apr_array_header_t *iproplist,
1071 svn_ra_session_t *session,
1072 apr_pool_t *result_pool,
1073 apr_pool_t *scratch_pool)
1077 const char *repos_root_url;
1078 apr_pool_t *iterpool;
1080 if (iproplist == NULL)
1082 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1083 capability we shouldn't be asking for inherited props, but if we
1084 did and the server sent back nothing then we'll want to handle
1086 *inherited_props = NULL;
1087 return SVN_NO_ERROR;
1090 SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
1092 *inherited_props = apr_array_make(
1093 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1095 iterpool = svn_pool_create(scratch_pool);
1097 for (i = 0; i < iproplist->nelts; i++)
1099 apr_array_header_t *iprop_list;
1100 char *parent_rel_path;
1102 apr_hash_index_t *hi;
1103 svn_prop_inherited_item_t *new_iprop =
1104 apr_palloc(result_pool, sizeof(*new_iprop));
1105 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1107 if (elt->kind != SVN_RA_SVN_LIST)
1108 return svn_error_create(
1109 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1110 _("Inherited proplist element not a list"));
1112 svn_pool_clear(iterpool);
1114 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1115 &parent_rel_path, &iprop_list));
1116 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1117 new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
1120 new_iprop->prop_hash = apr_hash_make(result_pool);
1121 for (hi = apr_hash_first(iterpool, iprops);
1123 hi = apr_hash_next(hi))
1125 const char *name = svn__apr_hash_index_key(hi);
1126 svn_string_t *value = svn__apr_hash_index_val(hi);
1127 svn_hash_sets(new_iprop->prop_hash,
1128 apr_pstrdup(result_pool, name),
1129 svn_string_dup(value, result_pool));
1131 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1134 svn_pool_destroy(iterpool);
1135 return SVN_NO_ERROR;
1138 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1139 svn_revnum_t rev, svn_stream_t *stream,
1140 svn_revnum_t *fetched_rev,
1144 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1145 svn_ra_svn_conn_t *conn = sess_baton->conn;
1146 apr_array_header_t *proplist;
1147 const char *expected_digest;
1148 svn_checksum_t *expected_checksum = NULL;
1149 svn_checksum_ctx_t *checksum_ctx;
1150 apr_pool_t *iterpool;
1152 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1153 (props != NULL), (stream != NULL)));
1154 SVN_ERR(handle_auth_request(sess_baton, pool));
1155 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1162 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1164 /* We're done if the contents weren't wanted. */
1166 return SVN_NO_ERROR;
1168 if (expected_digest)
1170 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1171 expected_digest, pool));
1172 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1175 /* Read the file's contents. */
1176 iterpool = svn_pool_create(pool);
1179 svn_ra_svn_item_t *item;
1181 svn_pool_clear(iterpool);
1182 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1183 if (item->kind != SVN_RA_SVN_STRING)
1184 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1185 _("Non-string as part of file contents"));
1186 if (item->u.string->len == 0)
1189 if (expected_checksum)
1190 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1191 item->u.string->len));
1193 SVN_ERR(svn_stream_write(stream, item->u.string->data,
1194 &item->u.string->len));
1196 svn_pool_destroy(iterpool);
1198 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1200 if (expected_checksum)
1202 svn_checksum_t *checksum;
1204 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1205 if (!svn_checksum_match(checksum, expected_checksum))
1206 return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1207 _("Checksum mismatch for '%s'"),
1211 return SVN_NO_ERROR;
1214 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1215 apr_hash_t **dirents,
1216 svn_revnum_t *fetched_rev,
1220 apr_uint32_t dirent_fields,
1223 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1224 svn_ra_svn_conn_t *conn = sess_baton->conn;
1225 apr_array_header_t *proplist, *dirlist;
1228 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1229 rev, (props != NULL), (dirents != NULL)));
1230 if (dirent_fields & SVN_DIRENT_KIND)
1231 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1232 if (dirent_fields & SVN_DIRENT_SIZE)
1233 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1234 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1235 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1236 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1237 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1238 if (dirent_fields & SVN_DIRENT_TIME)
1239 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1240 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1241 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1243 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1245 SVN_ERR(handle_auth_request(sess_baton, pool));
1246 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1252 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1254 /* We're done if dirents aren't wanted. */
1256 return SVN_NO_ERROR;
1258 /* Interpret the directory list. */
1259 *dirents = apr_hash_make(pool);
1260 for (i = 0; i < dirlist->nelts; i++)
1262 const char *name, *kind, *cdate, *cauthor;
1263 svn_boolean_t has_props;
1264 svn_dirent_t *dirent;
1267 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1269 if (elt->kind != SVN_RA_SVN_LIST)
1270 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1271 _("Dirlist element not a list"));
1272 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1273 &name, &kind, &size, &has_props,
1274 &crev, &cdate, &cauthor));
1275 name = svn_relpath_canonicalize(name, pool);
1276 dirent = svn_dirent_create(pool);
1277 dirent->kind = svn_node_kind_from_word(kind);
1278 dirent->size = size;/* FIXME: svn_filesize_t */
1279 dirent->has_props = has_props;
1280 dirent->created_rev = crev;
1281 /* NOTE: the tuple's format string says CDATE may be NULL. But this
1282 function does not allow that. The server has always sent us some
1283 random date, however, so this just happens to work. But let's
1284 be wary of servers that are (improperly) fixed to send NULL.
1286 Note: they should NOT be "fixed" to send NULL, as that would break
1287 any older clients which received that NULL. But we may as well
1288 be defensive against a malicous server. */
1292 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1293 dirent->last_author = cauthor;
1294 svn_hash_sets(*dirents, name, dirent);
1297 return SVN_NO_ERROR;
1300 /* Converts a apr_uint64_t with values TRUE, FALSE or
1301 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1302 to a svn_tristate_t */
1303 static svn_tristate_t
1304 optbool_to_tristate(apr_uint64_t v)
1306 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
1307 return svn_tristate_true;
1309 return svn_tristate_false;
1311 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1314 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1315 server, which defaults to youngest. */
1316 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1317 svn_mergeinfo_catalog_t *catalog,
1318 const apr_array_header_t *paths,
1319 svn_revnum_t revision,
1320 svn_mergeinfo_inheritance_t inherit,
1321 svn_boolean_t include_descendants,
1324 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1325 svn_ra_svn_conn_t *conn = sess_baton->conn;
1327 apr_array_header_t *mergeinfo_tuple;
1328 svn_ra_svn_item_t *elt;
1331 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1332 for (i = 0; i < paths->nelts; i++)
1334 path = APR_ARRAY_IDX(paths, i, const char *);
1335 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1337 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1338 svn_inheritance_to_word(inherit),
1339 include_descendants));
1341 SVN_ERR(handle_auth_request(sess_baton, pool));
1342 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1345 if (mergeinfo_tuple->nelts > 0)
1347 *catalog = apr_hash_make(pool);
1348 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1350 svn_mergeinfo_t for_path;
1351 const char *to_parse;
1353 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1354 if (elt->kind != SVN_RA_SVN_LIST)
1355 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1356 _("Mergeinfo element is not a list"));
1357 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1359 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1360 /* Correct for naughty servers that send "relative" paths
1361 with leading slashes! */
1362 svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1366 return SVN_NO_ERROR;
1369 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1370 const svn_ra_reporter3_t **reporter,
1371 void **report_baton, svn_revnum_t rev,
1372 const char *target, svn_depth_t depth,
1373 svn_boolean_t send_copyfrom_args,
1374 svn_boolean_t ignore_ancestry,
1375 const svn_delta_editor_t *update_editor,
1378 apr_pool_t *scratch_pool)
1380 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1381 svn_ra_svn_conn_t *conn = sess_baton->conn;
1382 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1384 /* Tell the server we want to start an update. */
1385 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1386 depth, send_copyfrom_args,
1388 SVN_ERR(handle_auth_request(sess_baton, pool));
1390 /* Fetch a reporter for the caller to drive. The reporter will drive
1391 * update_editor upon finish_report(). */
1392 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1393 target, depth, reporter, report_baton));
1394 return SVN_NO_ERROR;
1397 static svn_error_t *
1398 ra_svn_switch(svn_ra_session_t *session,
1399 const svn_ra_reporter3_t **reporter,
1400 void **report_baton, svn_revnum_t rev,
1401 const char *target, svn_depth_t depth,
1402 const char *switch_url,
1403 svn_boolean_t send_copyfrom_args,
1404 svn_boolean_t ignore_ancestry,
1405 const svn_delta_editor_t *update_editor,
1407 apr_pool_t *result_pool,
1408 apr_pool_t *scratch_pool)
1410 apr_pool_t *pool = result_pool;
1411 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1412 svn_ra_svn_conn_t *conn = sess_baton->conn;
1413 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1415 /* Tell the server we want to start a switch. */
1416 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1418 send_copyfrom_args, ignore_ancestry));
1419 SVN_ERR(handle_auth_request(sess_baton, pool));
1421 /* Fetch a reporter for the caller to drive. The reporter will drive
1422 * update_editor upon finish_report(). */
1423 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1424 target, depth, reporter, report_baton));
1425 return SVN_NO_ERROR;
1428 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1429 const svn_ra_reporter3_t **reporter,
1430 void **report_baton,
1431 const char *target, svn_revnum_t rev,
1433 const svn_delta_editor_t *status_editor,
1434 void *status_baton, apr_pool_t *pool)
1436 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1437 svn_ra_svn_conn_t *conn = sess_baton->conn;
1438 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1440 /* Tell the server we want to start a status operation. */
1441 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1443 SVN_ERR(handle_auth_request(sess_baton, pool));
1445 /* Fetch a reporter for the caller to drive. The reporter will drive
1446 * status_editor upon finish_report(). */
1447 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1448 target, depth, reporter, report_baton));
1449 return SVN_NO_ERROR;
1452 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1453 const svn_ra_reporter3_t **reporter,
1454 void **report_baton,
1455 svn_revnum_t rev, const char *target,
1457 svn_boolean_t ignore_ancestry,
1458 svn_boolean_t text_deltas,
1459 const char *versus_url,
1460 const svn_delta_editor_t *diff_editor,
1461 void *diff_baton, apr_pool_t *pool)
1463 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1464 svn_ra_svn_conn_t *conn = sess_baton->conn;
1465 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1467 /* Tell the server we want to start a diff. */
1468 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1469 ignore_ancestry, versus_url,
1470 text_deltas, depth));
1471 SVN_ERR(handle_auth_request(sess_baton, pool));
1473 /* Fetch a reporter for the caller to drive. The reporter will drive
1474 * diff_editor upon finish_report(). */
1475 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1476 target, depth, reporter, report_baton));
1477 return SVN_NO_ERROR;
1481 static svn_error_t *
1482 perform_ra_svn_log(svn_error_t **outer_error,
1483 svn_ra_session_t *session,
1484 const apr_array_header_t *paths,
1485 svn_revnum_t start, svn_revnum_t end,
1487 svn_boolean_t discover_changed_paths,
1488 svn_boolean_t strict_node_history,
1489 svn_boolean_t include_merged_revisions,
1490 const apr_array_header_t *revprops,
1491 svn_log_entry_receiver_t receiver,
1492 void *receiver_baton,
1495 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1496 svn_ra_svn_conn_t *conn = sess_baton->conn;
1497 apr_pool_t *iterpool;
1502 svn_boolean_t want_custom_revprops;
1504 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1507 for (i = 0; i < paths->nelts; i++)
1509 path = APR_ARRAY_IDX(paths, i, const char *);
1510 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1513 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1514 discover_changed_paths, strict_node_history,
1515 (apr_uint64_t) limit,
1516 include_merged_revisions));
1519 want_custom_revprops = FALSE;
1520 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1521 for (i = 0; i < revprops->nelts; i++)
1523 name = APR_ARRAY_IDX(revprops, i, char *);
1524 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1525 if (!want_custom_revprops
1526 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
1527 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
1528 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
1529 want_custom_revprops = TRUE;
1531 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1535 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1536 want_custom_revprops = TRUE;
1539 SVN_ERR(handle_auth_request(sess_baton, pool));
1541 /* Read the log messages. */
1542 iterpool = svn_pool_create(pool);
1545 apr_uint64_t has_children_param, invalid_revnum_param;
1546 apr_uint64_t has_subtractive_merge_param;
1547 svn_string_t *author, *date, *message;
1548 apr_array_header_t *cplist, *rplist;
1549 svn_log_entry_t *log_entry;
1550 svn_boolean_t has_children;
1551 svn_boolean_t subtractive_merge = FALSE;
1552 apr_uint64_t revprop_count;
1553 svn_ra_svn_item_t *item;
1558 svn_pool_clear(iterpool);
1559 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1560 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1562 if (item->kind != SVN_RA_SVN_LIST)
1563 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1564 _("Log entry not a list"));
1565 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1566 "lr(?s)(?s)(?s)?BBnl?B",
1567 &cplist, &rev, &author, &date,
1568 &message, &has_children_param,
1569 &invalid_revnum_param,
1570 &revprop_count, &rplist,
1571 &has_subtractive_merge_param));
1572 if (want_custom_revprops && rplist == NULL)
1574 /* Caller asked for custom revprops, but server is too old. */
1575 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1576 _("Server does not support custom revprops"
1580 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1581 has_children = FALSE;
1583 has_children = (svn_boolean_t) has_children_param;
1585 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1586 subtractive_merge = FALSE;
1588 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1590 /* Because the svn protocol won't let us send an invalid revnum, we have
1591 to recover that fact using the extra parameter. */
1592 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1593 && invalid_revnum_param)
1594 rev = SVN_INVALID_REVNUM;
1596 if (cplist->nelts > 0)
1598 /* Interpret the changed-paths list. */
1599 cphash = apr_hash_make(iterpool);
1600 for (i = 0; i < cplist->nelts; i++)
1602 svn_log_changed_path2_t *change;
1603 const char *copy_path, *action, *cpath, *kind_str;
1604 apr_uint64_t text_mods, prop_mods;
1605 svn_revnum_t copy_rev;
1606 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1609 if (elt->kind != SVN_RA_SVN_LIST)
1610 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1611 _("Changed-path entry not a list"));
1612 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool,
1614 &cpath, &action, ©_path,
1615 ©_rev, &kind_str,
1616 &text_mods, &prop_mods));
1617 cpath = svn_fspath__canonicalize(cpath, iterpool);
1619 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1620 change = svn_log_changed_path2_create(iterpool);
1621 change->action = *action;
1622 change->copyfrom_path = copy_path;
1623 change->copyfrom_rev = copy_rev;
1624 change->node_kind = svn_node_kind_from_word(kind_str);
1625 change->text_modified = optbool_to_tristate(text_mods);
1626 change->props_modified = optbool_to_tristate(prop_mods);
1627 svn_hash_sets(cphash, cpath, change);
1634 if (! (limit && (nest_level == 0) && (++nreceived > limit))
1638 log_entry = svn_log_entry_create(iterpool);
1640 log_entry->changed_paths = cphash;
1641 log_entry->changed_paths2 = cphash;
1642 log_entry->revision = rev;
1643 log_entry->has_children = has_children;
1644 log_entry->subtractive_merge = subtractive_merge;
1646 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1647 &log_entry->revprops));
1648 if (log_entry->revprops == NULL)
1649 log_entry->revprops = apr_hash_make(iterpool);
1650 if (revprops == NULL)
1652 /* Caller requested all revprops; set author/date/log. */
1654 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1657 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1660 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG,
1665 /* Caller requested some; maybe set author/date/log. */
1666 for (i = 0; i < revprops->nelts; i++)
1668 name = APR_ARRAY_IDX(revprops, i, char *);
1669 if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1670 svn_hash_sets(log_entry->revprops,
1671 SVN_PROP_REVISION_AUTHOR, author);
1672 if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1673 svn_hash_sets(log_entry->revprops,
1674 SVN_PROP_REVISION_DATE, date);
1675 if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1676 svn_hash_sets(log_entry->revprops,
1677 SVN_PROP_REVISION_LOG, message);
1680 err = receiver(receiver_baton, log_entry, iterpool);
1681 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1683 *outer_error = svn_error_trace(
1684 svn_error_compose_create(*outer_error, err));
1689 if (log_entry->has_children)
1693 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1695 SVN_ERR_ASSERT(nest_level);
1700 svn_pool_destroy(iterpool);
1702 /* Read the response. */
1703 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1706 static svn_error_t *
1707 ra_svn_log(svn_ra_session_t *session,
1708 const apr_array_header_t *paths,
1709 svn_revnum_t start, svn_revnum_t end,
1711 svn_boolean_t discover_changed_paths,
1712 svn_boolean_t strict_node_history,
1713 svn_boolean_t include_merged_revisions,
1714 const apr_array_header_t *revprops,
1715 svn_log_entry_receiver_t receiver,
1716 void *receiver_baton, apr_pool_t *pool)
1718 svn_error_t *outer_error = NULL;
1721 err = svn_error_trace(perform_ra_svn_log(&outer_error,
1725 discover_changed_paths,
1726 strict_node_history,
1727 include_merged_revisions,
1729 receiver, receiver_baton,
1731 return svn_error_trace(
1732 svn_error_compose_create(outer_error,
1738 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1739 const char *path, svn_revnum_t rev,
1740 svn_node_kind_t *kind, apr_pool_t *pool)
1742 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1743 svn_ra_svn_conn_t *conn = sess_baton->conn;
1744 const char *kind_word;
1746 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1747 SVN_ERR(handle_auth_request(sess_baton, pool));
1748 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1749 *kind = svn_node_kind_from_word(kind_word);
1750 return SVN_NO_ERROR;
1754 /* If ERR is a command not supported error, wrap it in a
1755 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1756 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1759 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1760 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1766 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1767 const char *path, svn_revnum_t rev,
1768 svn_dirent_t **dirent, apr_pool_t *pool)
1770 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1771 svn_ra_svn_conn_t *conn = sess_baton->conn;
1772 apr_array_header_t *list = NULL;
1773 svn_dirent_t *the_dirent;
1775 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1776 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1777 N_("'stat' not implemented")));
1778 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1786 const char *kind, *cdate, *cauthor;
1787 svn_boolean_t has_props;
1791 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1792 &kind, &size, &has_props,
1793 &crev, &cdate, &cauthor));
1795 the_dirent = svn_dirent_create(pool);
1796 the_dirent->kind = svn_node_kind_from_word(kind);
1797 the_dirent->size = size;/* FIXME: svn_filesize_t */
1798 the_dirent->has_props = has_props;
1799 the_dirent->created_rev = crev;
1800 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1801 the_dirent->last_author = cauthor;
1803 *dirent = the_dirent;
1806 return SVN_NO_ERROR;
1810 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1811 apr_hash_t **locations,
1813 svn_revnum_t peg_revision,
1814 const apr_array_header_t *location_revisions,
1817 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1818 svn_ra_svn_conn_t *conn = sess_baton->conn;
1819 svn_revnum_t revision;
1820 svn_boolean_t is_done;
1823 /* Transmit the parameters. */
1824 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1825 "get-locations", path, peg_revision));
1826 for (i = 0; i < location_revisions->nelts; i++)
1828 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1829 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1832 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1834 /* Servers before 1.1 don't support this command. Check for this here. */
1835 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1836 N_("'get-locations' not implemented")));
1838 /* Read the hash items. */
1840 *locations = apr_hash_make(pool);
1843 svn_ra_svn_item_t *item;
1844 const char *ret_path;
1846 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1847 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1849 else if (item->kind != SVN_RA_SVN_LIST)
1850 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1851 _("Location entry not a list"));
1854 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1855 &revision, &ret_path));
1856 ret_path = svn_fspath__canonicalize(ret_path, pool);
1857 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1859 sizeof(revision), ret_path);
1863 /* Read the response. This is so the server would have a chance to
1864 * report an error. */
1865 return svn_ra_svn__read_cmd_response(conn, pool, "");
1868 static svn_error_t *
1869 ra_svn_get_location_segments(svn_ra_session_t *session,
1871 svn_revnum_t peg_revision,
1872 svn_revnum_t start_rev,
1873 svn_revnum_t end_rev,
1874 svn_location_segment_receiver_t receiver,
1875 void *receiver_baton,
1878 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1879 svn_ra_svn_conn_t *conn = sess_baton->conn;
1880 svn_boolean_t is_done;
1881 apr_pool_t *iterpool = svn_pool_create(pool);
1883 /* Transmit the parameters. */
1884 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
1885 "get-location-segments",
1886 path, peg_revision, start_rev, end_rev));
1888 /* Servers before 1.5 don't support this command. Check for this here. */
1889 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1890 N_("'get-location-segments'"
1891 " not implemented")));
1893 /* Parse the response. */
1897 svn_revnum_t range_start, range_end;
1898 svn_ra_svn_item_t *item;
1899 const char *ret_path;
1901 svn_pool_clear(iterpool);
1902 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1903 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1905 else if (item->kind != SVN_RA_SVN_LIST)
1906 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1907 _("Location segment entry not a list"));
1910 svn_location_segment_t *segment = apr_pcalloc(iterpool,
1912 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
1913 &range_start, &range_end, &ret_path));
1914 if (! (SVN_IS_VALID_REVNUM(range_start)
1915 && SVN_IS_VALID_REVNUM(range_end)))
1916 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1917 _("Expected valid revision range"));
1919 ret_path = svn_relpath_canonicalize(ret_path, iterpool);
1920 segment->path = ret_path;
1921 segment->range_start = range_start;
1922 segment->range_end = range_end;
1923 SVN_ERR(receiver(segment, receiver_baton, iterpool));
1926 svn_pool_destroy(iterpool);
1928 /* Read the response. This is so the server would have a chance to
1929 * report an error. */
1930 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1932 return SVN_NO_ERROR;
1935 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
1937 svn_revnum_t start, svn_revnum_t end,
1938 svn_boolean_t include_merged_revisions,
1939 svn_file_rev_handler_t handler,
1940 void *handler_baton, apr_pool_t *pool)
1942 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1943 apr_pool_t *rev_pool, *chunk_pool;
1944 svn_boolean_t has_txdelta;
1945 svn_boolean_t had_revision = FALSE;
1947 /* One sub-pool for each revision and one for each txdelta chunk.
1948 Note that the rev_pool must live during the following txdelta. */
1949 rev_pool = svn_pool_create(pool);
1950 chunk_pool = svn_pool_create(pool);
1952 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
1954 include_merged_revisions));
1956 /* Servers before 1.1 don't support this command. Check for this here. */
1957 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1958 N_("'get-file-revs' not implemented")));
1962 apr_array_header_t *rev_proplist, *proplist;
1963 apr_uint64_t merged_rev_param;
1964 apr_array_header_t *props;
1965 svn_ra_svn_item_t *item;
1966 apr_hash_t *rev_props;
1969 svn_boolean_t merged_rev;
1970 svn_txdelta_window_handler_t d_handler;
1973 svn_pool_clear(rev_pool);
1974 svn_pool_clear(chunk_pool);
1975 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
1976 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1978 /* Either we've got a correct revision or we will error out below. */
1979 had_revision = TRUE;
1980 if (item->kind != SVN_RA_SVN_LIST)
1981 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1982 _("Revision entry not a list"));
1984 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
1985 "crll?B", &p, &rev, &rev_proplist,
1986 &proplist, &merged_rev_param));
1987 p = svn_fspath__canonicalize(p, rev_pool);
1988 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
1989 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
1990 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1993 merged_rev = (svn_boolean_t) merged_rev_param;
1995 /* Get the first delta chunk so we know if there is a delta. */
1996 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
1997 if (item->kind != SVN_RA_SVN_STRING)
1998 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1999 _("Text delta chunk not a string"));
2000 has_txdelta = item->u.string->len > 0;
2002 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2003 has_txdelta ? &d_handler : NULL, &d_baton,
2006 /* Process the text delta if any. */
2009 svn_stream_t *stream;
2012 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2016 while (item->u.string->len > 0)
2020 size = item->u.string->len;
2022 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2023 svn_pool_clear(chunk_pool);
2025 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2027 if (item->kind != SVN_RA_SVN_STRING)
2028 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2029 _("Text delta chunk not a string"));
2032 SVN_ERR(svn_stream_close(stream));
2036 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2038 /* Return error if we didn't get any revisions. */
2040 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2041 _("The get-file-revs command didn't return "
2044 svn_pool_destroy(chunk_pool);
2045 svn_pool_destroy(rev_pool);
2047 return SVN_NO_ERROR;
2050 /* For each path in PATH_REVS, send a 'lock' command to the server.
2051 Used with 1.2.x series servers which support locking, but of only
2052 one path at a time. ra_svn_lock(), which supports 'lock-many'
2053 is now the default. See svn_ra_lock() docstring for interface details. */
2054 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2055 apr_hash_t *path_revs,
2056 const char *comment,
2057 svn_boolean_t steal_lock,
2058 svn_ra_lock_callback_t lock_func,
2062 svn_ra_svn__session_baton_t *sess = session->priv;
2063 svn_ra_svn_conn_t* conn = sess->conn;
2064 apr_array_header_t *list;
2065 apr_hash_index_t *hi;
2066 apr_pool_t *iterpool = svn_pool_create(pool);
2068 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2074 svn_revnum_t *revnum;
2075 svn_error_t *err, *callback_err = NULL;
2077 svn_pool_clear(iterpool);
2079 apr_hash_this(hi, &key, NULL, &val);
2083 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2084 steal_lock, *revnum));
2086 /* Servers before 1.2 doesn't support locking. Check this here. */
2087 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2088 N_("Server doesn't support "
2089 "the lock command")));
2091 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2094 SVN_ERR(parse_lock(list, iterpool, &lock));
2096 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2100 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2103 svn_error_clear(err);
2106 return callback_err;
2109 svn_pool_destroy(iterpool);
2111 return SVN_NO_ERROR;
2114 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2115 Used with 1.2.x series servers which support unlocking, but of only
2116 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
2117 now the default. See svn_ra_unlock() docstring for interface details. */
2118 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2119 apr_hash_t *path_tokens,
2120 svn_boolean_t break_lock,
2121 svn_ra_lock_callback_t lock_func,
2125 svn_ra_svn__session_baton_t *sess = session->priv;
2126 svn_ra_svn_conn_t* conn = sess->conn;
2127 apr_hash_index_t *hi;
2128 apr_pool_t *iterpool = svn_pool_create(pool);
2130 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2136 svn_error_t *err, *callback_err = NULL;
2138 svn_pool_clear(iterpool);
2140 apr_hash_this(hi, &key, NULL, &val);
2142 if (strcmp(val, "") != 0)
2147 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2150 /* Servers before 1.2 don't support locking. Check this here. */
2151 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2152 N_("Server doesn't support the unlock "
2155 err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2157 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2161 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2163 svn_error_clear(err);
2166 return callback_err;
2169 svn_pool_destroy(iterpool);
2171 return SVN_NO_ERROR;
2174 /* Tell the server to lock all paths in PATH_REVS.
2175 See svn_ra_lock() for interface details. */
2176 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2177 apr_hash_t *path_revs,
2178 const char *comment,
2179 svn_boolean_t steal_lock,
2180 svn_ra_lock_callback_t lock_func,
2184 svn_ra_svn__session_baton_t *sess = session->priv;
2185 svn_ra_svn_conn_t *conn = sess->conn;
2186 apr_hash_index_t *hi;
2188 apr_pool_t *iterpool = svn_pool_create(pool);
2190 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2191 comment, steal_lock));
2193 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2198 svn_revnum_t *revnum;
2200 svn_pool_clear(iterpool);
2201 apr_hash_this(hi, &key, NULL, &val);
2205 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2208 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2210 err = handle_auth_request(sess, pool);
2212 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2214 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2216 svn_error_clear(err);
2217 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2218 lock_func, lock_baton, pool);
2224 /* Loop over responses to get lock information. */
2225 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2227 svn_ra_svn_item_t *elt;
2230 svn_error_t *callback_err;
2233 apr_array_header_t *list;
2235 apr_hash_this(hi, &key, NULL, NULL);
2238 svn_pool_clear(iterpool);
2239 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2241 /* The server might have encountered some sort of fatal error in
2242 the middle of the request list. If this happens, it will
2243 transmit "done" to end the lock-info early, and then the
2244 overall command response will talk about the fatal error. */
2245 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2248 if (elt->kind != SVN_RA_SVN_LIST)
2249 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2250 _("Lock response not a list"));
2252 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2255 if (strcmp(status, "failure") == 0)
2256 err = svn_ra_svn__handle_failure_status(list, iterpool);
2257 else if (strcmp(status, "success") == 0)
2259 SVN_ERR(parse_lock(list, iterpool, &lock));
2263 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2264 _("Unknown status for lock command"));
2267 callback_err = lock_func(lock_baton, path, TRUE,
2271 callback_err = SVN_NO_ERROR;
2273 svn_error_clear(err);
2276 return callback_err;
2279 /* If we didn't break early above, and the whole hash was traversed,
2280 read the final "done" from the server. */
2283 svn_ra_svn_item_t *elt;
2285 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2286 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2287 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2288 _("Didn't receive end marker for lock "
2292 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2294 svn_pool_destroy(iterpool);
2296 return SVN_NO_ERROR;
2299 /* Tell the server to unlock all paths in PATH_TOKENS.
2300 See svn_ra_unlock() for interface details. */
2301 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2302 apr_hash_t *path_tokens,
2303 svn_boolean_t break_lock,
2304 svn_ra_lock_callback_t lock_func,
2308 svn_ra_svn__session_baton_t *sess = session->priv;
2309 svn_ra_svn_conn_t *conn = sess->conn;
2310 apr_hash_index_t *hi;
2311 apr_pool_t *iterpool = svn_pool_create(pool);
2315 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2318 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2324 svn_pool_clear(iterpool);
2325 apr_hash_this(hi, &key, NULL, &val);
2328 if (strcmp(val, "") != 0)
2333 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2336 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2338 err = handle_auth_request(sess, pool);
2340 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2343 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2345 svn_error_clear(err);
2346 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2353 /* Loop over responses to unlock files. */
2354 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2356 svn_ra_svn_item_t *elt;
2358 svn_error_t *callback_err;
2360 apr_array_header_t *list;
2362 svn_pool_clear(iterpool);
2364 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2366 /* The server might have encountered some sort of fatal error in
2367 the middle of the request list. If this happens, it will
2368 transmit "done" to end the lock-info early, and then the
2369 overall command response will talk about the fatal error. */
2370 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2373 apr_hash_this(hi, &key, NULL, NULL);
2376 if (elt->kind != SVN_RA_SVN_LIST)
2377 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2378 _("Unlock response not a list"));
2380 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2383 if (strcmp(status, "failure") == 0)
2384 err = svn_ra_svn__handle_failure_status(list, iterpool);
2385 else if (strcmp(status, "success") == 0)
2387 SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2391 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2392 _("Unknown status for unlock command"));
2395 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2398 callback_err = SVN_NO_ERROR;
2400 svn_error_clear(err);
2403 return callback_err;
2406 /* If we didn't break early above, and the whole hash was traversed,
2407 read the final "done" from the server. */
2410 svn_ra_svn_item_t *elt;
2412 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2413 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2414 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2415 _("Didn't receive end marker for unlock "
2419 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2421 svn_pool_destroy(iterpool);
2423 return SVN_NO_ERROR;
2426 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2431 svn_ra_svn__session_baton_t *sess = session->priv;
2432 svn_ra_svn_conn_t* conn = sess->conn;
2433 apr_array_header_t *list;
2435 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2437 /* Servers before 1.2 doesn't support locking. Check this here. */
2438 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2439 N_("Server doesn't support the get-lock "
2442 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2444 SVN_ERR(parse_lock(list, pool, lock));
2448 return SVN_NO_ERROR;
2451 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2452 to prevent a dependency cycle. */
2453 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2454 const char **rel_path,
2458 const char *root_url;
2460 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2461 *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2463 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2464 _("'%s' isn't a child of repository root "
2467 return SVN_NO_ERROR;
2470 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2476 svn_ra_svn__session_baton_t *sess = session->priv;
2477 svn_ra_svn_conn_t* conn = sess->conn;
2478 apr_array_header_t *list;
2479 const char *full_url, *abs_path;
2482 /* Figure out the repository abspath from PATH. */
2483 full_url = svn_path_url_add_component2(sess->url, path, pool);
2484 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2485 abs_path = svn_fspath__canonicalize(abs_path, pool);
2487 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2489 /* Servers before 1.2 doesn't support locking. Check this here. */
2490 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2491 N_("Server doesn't support the get-lock "
2494 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2496 *locks = apr_hash_make(pool);
2498 for (i = 0; i < list->nelts; ++i)
2501 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2503 if (elt->kind != SVN_RA_SVN_LIST)
2504 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2505 _("Lock element not a list"));
2506 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2508 /* Filter out unwanted paths. Since Subversion only allows
2509 locks on files, we can treat depth=immediates the same as
2510 depth=files for filtering purposes. Meaning, we'll keep
2513 a) its path is the very path we queried, or
2514 b) we've asked for a fully recursive answer, or
2515 c) we've asked for depth=files or depth=immediates, and this
2516 lock is on an immediate child of our query path.
2518 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2520 svn_hash_sets(*locks, lock->path, lock);
2522 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2524 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2525 if (relpath && (svn_path_component_count(relpath) == 1))
2526 svn_hash_sets(*locks, lock->path, lock);
2530 return SVN_NO_ERROR;
2534 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2535 svn_revnum_t revision,
2536 svn_revnum_t low_water_mark,
2537 svn_boolean_t send_deltas,
2538 const svn_delta_editor_t *editor,
2542 svn_ra_svn__session_baton_t *sess = session->priv;
2544 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2545 low_water_mark, send_deltas));
2547 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2548 N_("Server doesn't support the replay "
2551 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2554 return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
2558 static svn_error_t *
2559 ra_svn_replay_range(svn_ra_session_t *session,
2560 svn_revnum_t start_revision,
2561 svn_revnum_t end_revision,
2562 svn_revnum_t low_water_mark,
2563 svn_boolean_t send_deltas,
2564 svn_ra_replay_revstart_callback_t revstart_func,
2565 svn_ra_replay_revfinish_callback_t revfinish_func,
2569 svn_ra_svn__session_baton_t *sess = session->priv;
2570 apr_pool_t *iterpool;
2572 svn_boolean_t drive_aborted = FALSE;
2574 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2575 start_revision, end_revision,
2576 low_water_mark, send_deltas));
2578 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2579 N_("Server doesn't support the "
2580 "replay-range command")));
2582 iterpool = svn_pool_create(pool);
2583 for (rev = start_revision; rev <= end_revision; rev++)
2585 const svn_delta_editor_t *editor;
2587 apr_hash_t *rev_props;
2589 apr_array_header_t *list;
2591 svn_pool_clear(iterpool);
2593 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2594 "wl", &word, &list));
2595 if (strcmp(word, "revprops") != 0)
2596 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2597 _("Expected 'revprops', found '%s'"),
2600 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2602 SVN_ERR(revstart_func(rev, replay_baton,
2603 &editor, &edit_baton,
2606 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2608 &drive_aborted, TRUE));
2609 /* If drive_editor2() aborted the commit, do NOT try to call
2610 revfinish_func and commit the transaction! */
2611 if (drive_aborted) {
2612 svn_pool_destroy(iterpool);
2613 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2614 _("Error while replaying commit"));
2616 SVN_ERR(revfinish_func(rev, replay_baton,
2621 svn_pool_destroy(iterpool);
2623 return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
2627 static svn_error_t *
2628 ra_svn_has_capability(svn_ra_session_t *session,
2630 const char *capability,
2633 svn_ra_svn__session_baton_t *sess = session->priv;
2634 static const char* capabilities[][2] =
2636 /* { ra capability string, svn:// wire capability string} */
2637 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2638 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2639 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2640 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2641 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2642 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2643 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2644 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2645 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2646 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2647 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2649 {NULL, NULL} /* End of list marker */
2655 for (i = 0; capabilities[i][0]; i++)
2657 if (strcmp(capability, capabilities[i][0]) == 0)
2659 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2660 return SVN_NO_ERROR;
2664 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2665 _("Don't know anything about capability '%s'"),
2669 static svn_error_t *
2670 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2672 svn_revnum_t peg_revision,
2673 svn_revnum_t end_revision,
2674 svn_revnum_t *revision_deleted,
2678 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2679 svn_ra_svn_conn_t *conn = sess_baton->conn;
2681 /* Transmit the parameters. */
2682 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2683 peg_revision, end_revision));
2685 /* Servers before 1.6 don't support this command. Check for this here. */
2686 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2687 N_("'get-deleted-rev' not implemented")));
2689 return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted);
2692 static svn_error_t *
2693 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2694 svn_delta_shim_callbacks_t *callbacks)
2696 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2697 svn_ra_svn_conn_t *conn = sess_baton->conn;
2699 conn->shim_callbacks = callbacks;
2701 return SVN_NO_ERROR;
2704 static svn_error_t *
2705 ra_svn_get_inherited_props(svn_ra_session_t *session,
2706 apr_array_header_t **iprops,
2708 svn_revnum_t revision,
2709 apr_pool_t *result_pool,
2710 apr_pool_t *scratch_pool)
2712 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2713 svn_ra_svn_conn_t *conn = sess_baton->conn;
2714 apr_array_header_t *iproplist;
2716 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2718 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2719 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2720 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2723 return SVN_NO_ERROR;
2726 static const svn_ra__vtable_t ra_svn_vtable = {
2728 ra_svn_get_description,
2732 ra_svn_get_session_url,
2733 ra_svn_get_latest_rev,
2734 ra_svn_get_dated_rev,
2735 ra_svn_change_rev_prop,
2736 ra_svn_rev_proplist,
2741 ra_svn_get_mergeinfo,
2750 ra_svn_get_repos_root,
2751 ra_svn_get_locations,
2752 ra_svn_get_location_segments,
2753 ra_svn_get_file_revs,
2759 ra_svn_has_capability,
2760 ra_svn_replay_range,
2761 ra_svn_get_deleted_rev,
2762 ra_svn_register_editor_shim_callbacks,
2763 ra_svn_get_inherited_props
2767 svn_ra_svn__init(const svn_version_t *loader_version,
2768 const svn_ra__vtable_t **vtable,
2771 static const svn_version_checklist_t checklist[] =
2773 { "svn_subr", svn_subr_version },
2774 { "svn_delta", svn_delta_version },
2778 SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist));
2780 /* Simplified version check to make sure we can safely use the
2781 VTABLE parameter. The RA loader does a more exhaustive check. */
2782 if (loader_version->major != SVN_VER_MAJOR)
2784 return svn_error_createf
2785 (SVN_ERR_VERSION_MISMATCH, NULL,
2786 _("Unsupported RA loader version (%d) for ra_svn"),
2787 loader_version->major);
2790 *vtable = &ra_svn_vtable;
2792 #ifdef SVN_HAVE_SASL
2793 SVN_ERR(svn_ra_svn__sasl_init());
2796 return SVN_NO_ERROR;
2799 /* Compatibility wrapper for the 1.1 and before API. */
2800 #define NAME "ra_svn"
2801 #define DESCRIPTION RA_SVN_DESCRIPTION
2802 #define VTBL ra_svn_vtable
2803 #define INITFUNC svn_ra_svn__init
2804 #define COMPAT_INITFUNC svn_ra_svn_init
2805 #include "../libsvn_ra/wrapper_template.h"