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"
53 #include "private/svn_subr_private.h"
55 #include "../libsvn_ra/ra_loader.h"
60 #define DO_AUTH svn_ra_svn__do_cyrus_auth
62 #define DO_AUTH svn_ra_svn__do_internal_auth
65 /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
66 whatever reason) deems svn_depth_immediates as non-recursive, which
67 is ... kinda true, but not true enough for our purposes. We need
68 our requested recursion level to be *at least* as recursive as the
69 real depth we're looking for.
71 #define DEPTH_TO_RECURSE(d) \
72 ((d) == svn_depth_unknown || (d) > svn_depth_files)
74 typedef struct ra_svn_commit_callback_baton_t {
75 svn_ra_svn__session_baton_t *sess_baton;
77 svn_revnum_t *new_rev;
78 svn_commit_callback2_t callback;
80 } ra_svn_commit_callback_baton_t;
82 typedef struct ra_svn_reporter_baton_t {
83 svn_ra_svn__session_baton_t *sess_baton;
84 svn_ra_svn_conn_t *conn;
86 const svn_delta_editor_t *editor;
88 } ra_svn_reporter_baton_t;
90 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
92 static void parse_tunnel(const char *url, const char **tunnel,
97 if (strncasecmp(url, "svn", 3) != 0)
101 /* Get the tunnel specification, if any. */
107 p = strchr(url, ':');
110 *tunnel = apr_pstrmemdup(pool, url, p - url);
114 static svn_error_t *make_connection(const char *hostname, unsigned short port,
115 apr_socket_t **sock, apr_pool_t *pool)
119 int family = APR_INET;
121 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
122 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
123 create IPV6 sockets. */
126 #ifdef MAX_SECS_TO_LINGER
127 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
129 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
130 APR_PROTO_TCP, pool);
134 apr_socket_close(*sock);
139 /* Resolve the hostname. */
140 status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
142 return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
144 /* Iterate through the returned list of addresses attempting to
145 * connect to each in turn. */
148 /* Create the socket. */
149 #ifdef MAX_SECS_TO_LINGER
150 /* ### old APR interface */
151 status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
153 status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
156 if (status == APR_SUCCESS)
158 status = apr_socket_connect(*sock, sa);
159 if (status != APR_SUCCESS)
160 apr_socket_close(*sock);
164 while (status != APR_SUCCESS && sa);
167 return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
170 /* Enable TCP keep-alives on the socket so we time out when
171 * the connection breaks due to network-layer problems.
172 * If the peer has dropped the connection due to a network partition
173 * or a crash, or if the peer no longer considers the connection
174 * valid because we are behind a NAT and our public IP has changed,
175 * it will respond to the keep-alive probe with a RST instead of an
176 * acknowledgment segment, which will cause svn to abort the session
177 * even while it is currently blocked waiting for data from the peer.
178 * See issue #3347. */
179 status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
182 /* It's not a fatal error if we cannot enable keep-alives. */
188 /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
189 property diffs in LIST, received from the server. */
190 static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
192 apr_array_header_t **diffs)
196 *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
198 for (i = 0; i < list->nelts; i++)
201 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
203 if (elt->kind != SVN_RA_SVN_LIST)
204 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
205 _("Prop diffs element not a list"));
206 prop = apr_array_push(*diffs);
207 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
213 /* Parse a lockdesc, provided in LIST as specified by the protocol into
214 LOCK, allocated in POOL. */
215 static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
218 const char *cdate, *edate;
219 *lock = svn_lock_create(pool);
220 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
221 &(*lock)->token, &(*lock)->owner,
222 &(*lock)->comment, &cdate, &edate));
223 (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
224 SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
226 SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
230 /* --- AUTHENTICATION ROUTINES --- */
232 svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
234 const char *mech, const char *mech_arg)
236 return svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg);
239 static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
242 svn_ra_svn_conn_t *conn = sess->conn;
243 apr_array_header_t *mechlist;
246 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
247 if (mechlist->nelts == 0)
249 return DO_AUTH(sess, mechlist, realm, pool);
252 /* --- REPORTER IMPLEMENTATION --- */
254 static svn_error_t *ra_svn_set_path(void *baton, const char *path,
257 svn_boolean_t start_empty,
258 const char *lock_token,
261 ra_svn_reporter_baton_t *b = baton;
263 SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
264 start_empty, lock_token, depth));
268 static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
271 ra_svn_reporter_baton_t *b = baton;
273 SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
277 static svn_error_t *ra_svn_link_path(void *baton, const char *path,
281 svn_boolean_t start_empty,
282 const char *lock_token,
285 ra_svn_reporter_baton_t *b = baton;
287 SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
288 start_empty, lock_token, depth));
292 static svn_error_t *ra_svn_finish_report(void *baton,
295 ra_svn_reporter_baton_t *b = baton;
297 SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
298 SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
299 SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
301 SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
305 static svn_error_t *ra_svn_abort_report(void *baton,
308 ra_svn_reporter_baton_t *b = baton;
310 SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
314 static svn_ra_reporter3_t ra_svn_reporter = {
318 ra_svn_finish_report,
322 /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
323 * EDITOR/EDIT_BATON when it gets the finish_report() call.
325 * Allocate the new reporter in POOL.
328 ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
330 const svn_delta_editor_t *editor,
334 const svn_ra_reporter3_t **reporter,
337 ra_svn_reporter_baton_t *b;
338 const svn_delta_editor_t *filter_editor;
341 /* We can skip the depth filtering when the user requested
342 depth_files or depth_infinity because the server will
343 transmit the right stuff anyway. */
344 if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
345 && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
347 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
349 editor, edit_baton, depth,
352 editor = filter_editor;
353 edit_baton = filter_baton;
356 b = apr_palloc(pool, sizeof(*b));
357 b->sess_baton = sess_baton;
358 b->conn = sess_baton->conn;
361 b->edit_baton = edit_baton;
363 *reporter = &ra_svn_reporter;
369 /* --- RA LAYER IMPLEMENTATION --- */
371 /* (Note: *ARGV is an output parameter.) */
372 static svn_error_t *find_tunnel_agent(const char *tunnel,
373 const char *hostinfo,
375 apr_hash_t *config, apr_pool_t *pool)
378 const char *val, *var, *cmd;
384 /* Look up the tunnel specification in config. */
385 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
386 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
388 /* We have one predefined tunnel scheme, if it isn't overridden by config. */
389 if (!val && strcmp(tunnel, "ssh") == 0)
391 /* Killing the tunnel agent with SIGTERM leads to unsightly
392 * stderr output from ssh, unless we pass -q.
393 * The "-q" option to ssh is widely supported: all versions of
394 * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
395 * versions have it too. If the user is using some other ssh
396 * implementation that doesn't accept it, they can override it
397 * in the [tunnels] section of the config. */
398 val = "$SVN_SSH ssh -q";
402 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
403 _("Undefined tunnel scheme '%s'"), tunnel);
405 /* If the scheme definition begins with "$varname", it means there
406 * is an environment variable which can override the command. */
410 len = strcspn(val, " ");
411 var = apr_pstrmemdup(pool, val, len);
419 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
420 _("Tunnel scheme %s requires environment "
421 "variable %s to be defined"), tunnel,
428 /* Tokenize the command into a list of arguments. */
429 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
430 if (status != APR_SUCCESS)
431 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
433 /* Append the fixed arguments to the result. */
434 for (n = 0; cmd_argv[n] != NULL; n++)
436 *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
437 memcpy((void *) *argv, cmd_argv, n * sizeof(char *));
438 (*argv)[n++] = svn_path_uri_decode(hostinfo, pool);
439 (*argv)[n++] = "svnserve";
446 /* This function handles any errors which occur in the child process
447 * created for a tunnel agent. We write the error out as a command
448 * failure; the code in ra_svn_open() to read the server's greeting
449 * will see the error and return it to the caller. */
450 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
453 svn_ra_svn_conn_t *conn;
454 apr_file_t *in_file, *out_file;
457 if (apr_file_open_stdin(&in_file, pool)
458 || apr_file_open_stdout(&out_file, pool))
461 conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
462 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
464 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
465 svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
466 svn_error_clear(err);
467 svn_error_clear(svn_ra_svn__flush(conn, pool));
470 /* (Note: *CONN is an output parameter.) */
471 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
476 apr_procattr_t *attr;
479 status = apr_procattr_create(&attr, pool);
480 if (status == APR_SUCCESS)
481 status = apr_procattr_io_set(attr, 1, 1, 0);
482 if (status == APR_SUCCESS)
483 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
484 if (status == APR_SUCCESS)
485 status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
486 proc = apr_palloc(pool, sizeof(*proc));
487 if (status == APR_SUCCESS)
488 status = apr_proc_create(proc, *args, args, NULL, attr, pool);
489 if (status != APR_SUCCESS)
490 return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
491 svn_error_wrap_apr(status,
492 _("Can't create tunnel")), NULL);
494 /* Arrange for the tunnel agent to get a SIGTERM on pool
495 * cleanup. This is a little extreme, but the alternatives
496 * weren't working out.
498 * Closing the pipes and waiting for the process to die
499 * was prone to mysterious hangs which are difficult to
500 * diagnose (e.g. svnserve dumps core due to unrelated bug;
501 * sshd goes into zombie state; ssh connection is never
502 * closed; ssh never terminates).
503 * See also the long dicussion in issue #2580 if you really
504 * want to know various reasons for these problems and
505 * the different opinions on this issue.
507 * On Win32, APR does not support KILL_ONLY_ONCE. It only has
508 * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
509 * KILL_ALWAYS, which immediately calls TerminateProcess().
510 * This instantly kills the tunnel, leaving sshd and svnserve
511 * on a remote machine running indefinitely. These processes
512 * accumulate. The problem is most often seen with a fast client
513 * machine and a modest internet connection, as the tunnel
514 * is killed before being able to gracefully complete the
515 * session. In that case, svn is unusable 100% of the time on
516 * the windows machine. Thus, on Win32, we use KILL_NEVER and
517 * take the lesser of two evils.
520 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
522 apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
525 /* APR pipe objects inherit by default. But we don't want the
526 * tunnel agent's pipes held open by future child processes
527 * (such as other ra_svn sessions), so turn that off. */
528 apr_file_inherit_unset(proc->in);
529 apr_file_inherit_unset(proc->out);
531 /* Guard against dotfile output to stdout on the server. */
532 *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in,
533 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
535 err = svn_ra_svn__skip_leading_garbage(*conn, pool);
537 return svn_error_quick_wrap(
539 _("To better debug SSH connection problems, remove the -q "
540 "option from 'ssh' in the [tunnels] section of your "
541 "Subversion configuration file."));
546 /* Parse URL inot URI, validating it and setting the default port if none
547 was given. Allocate the URI fileds out of POOL. */
548 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
551 apr_status_t apr_err;
553 apr_err = apr_uri_parse(pool, url, uri);
556 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
557 _("Illegal svn repository URL '%s'"), url);
560 uri->port = SVN_RA_SVN_PORT;
565 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
566 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
567 are provided by the caller of ra_svn_open. If tunnel_argv is non-null,
568 it points to a program argument list to use when invoking the tunnel agent.
570 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
572 const apr_uri_t *uri,
573 const char **tunnel_argv,
574 const svn_ra_callbacks2_t *callbacks,
575 void *callbacks_baton,
578 svn_ra_svn__session_baton_t *sess;
579 svn_ra_svn_conn_t *conn;
581 apr_uint64_t minver, maxver;
582 apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
583 const char *client_string = NULL;
585 sess = apr_palloc(pool, sizeof(*sess));
587 sess->is_tunneled = (tunnel_argv != NULL);
588 sess->url = apr_pstrdup(pool, url);
589 sess->user = uri->user;
590 sess->hostname = uri->hostname;
591 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
593 sess->tunnel_argv = tunnel_argv;
594 sess->callbacks = callbacks;
595 sess->callbacks_baton = callbacks_baton;
596 sess->bytes_read = sess->bytes_written = 0;
599 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
602 SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
603 conn = svn_ra_svn_create_conn3(sock, NULL, NULL,
604 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
608 /* Build the useragent string, querying the client for any
609 customizations it wishes to note. For historical reasons, we
610 still deliver the hard-coded client version info
611 (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
612 separately in the protocol/capabilities handshake below. But the
613 commit logic wants the combined form for use with the
614 SVN_PROP_TXN_USER_AGENT ephemeral property because that's
615 consistent with our DAV approach. */
616 if (sess->callbacks->get_client_string != NULL)
617 SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
618 &client_string, pool));
620 sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
621 client_string, (char *)NULL);
623 sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
625 /* Make sure we set conn->session before reading from it,
626 * because the reader and writer functions expect a non-NULL value. */
628 conn->session = sess;
630 /* Read server's greeting. */
631 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
632 &mechlist, &server_caplist));
634 /* We support protocol version 2. */
636 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
637 _("Server requires minimum version %d"),
640 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
641 _("Server only supports versions up to %d"),
643 SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
645 /* All released versions of Subversion support edit-pipeline,
646 * so we do not support servers that do not. */
647 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
648 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
649 _("Server does not support edit pipelining"));
651 /* In protocol version 2, we send back our protocol version, our
652 * capability list, and the URL, and subsequently there is an auth
654 /* Client-side capabilities list: */
655 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
657 SVN_RA_SVN_CAP_EDIT_PIPELINE,
658 SVN_RA_SVN_CAP_SVNDIFF1,
659 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
660 SVN_RA_SVN_CAP_DEPTH,
661 SVN_RA_SVN_CAP_MERGEINFO,
662 SVN_RA_SVN_CAP_LOG_REVPROPS,
664 SVN_RA_SVN__DEFAULT_USERAGENT,
666 SVN_ERR(handle_auth_request(sess, pool));
668 /* This is where the security layer would go into effect if we
669 * supported security layers, which is a ways off. */
671 /* Read the repository's uuid and root URL, and perhaps learn more
672 capabilities that weren't available before now. */
673 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
674 &conn->repos_root, &repos_caplist));
676 SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
678 if (conn->repos_root)
680 conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
681 /* We should check that the returned string is a prefix of url, since
682 that's the API guarantee, but this isn't true for 1.0 servers.
683 Checking the length prevents client crashes. */
684 if (strlen(conn->repos_root) > strlen(url))
685 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
686 _("Impossibly long repository root from "
697 #define RA_SVN_DESCRIPTION \
698 N_("Module for accessing a repository using the svn network protocol.\n" \
699 " - with Cyrus SASL authentication")
701 #define RA_SVN_DESCRIPTION \
702 N_("Module for accessing a repository using the svn network protocol.")
705 static const char *ra_svn_get_description(apr_pool_t *pool)
707 return _(RA_SVN_DESCRIPTION);
710 static const char * const *
711 ra_svn_get_schemes(apr_pool_t *pool)
713 static const char *schemes[] = { "svn", NULL };
720 static svn_error_t *ra_svn_open(svn_ra_session_t *session,
721 const char **corrected_url,
723 const svn_ra_callbacks2_t *callbacks,
724 void *callback_baton,
728 apr_pool_t *sess_pool = svn_pool_create(pool);
729 svn_ra_svn__session_baton_t *sess;
730 const char *tunnel, **tunnel_argv;
732 svn_config_t *cfg, *cfg_client;
734 /* We don't support server-prescribed redirections in ra-svn. */
736 *corrected_url = NULL;
738 SVN_ERR(parse_url(url, &uri, sess_pool));
740 parse_tunnel(url, &tunnel, pool);
743 SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
749 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
751 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
752 svn_auth_set_parameter(callbacks->auth_baton,
753 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
754 svn_auth_set_parameter(callbacks->auth_baton,
755 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
757 /* We open the session in a subpool so we can get rid of it if we
758 reparent with a server that doesn't support reparenting. */
759 SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
760 callbacks, callback_baton, sess_pool));
761 session->priv = sess;
766 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
770 svn_ra_svn__session_baton_t *sess = ra_session->priv;
771 svn_ra_svn_conn_t *conn = sess->conn;
773 apr_pool_t *sess_pool;
774 svn_ra_svn__session_baton_t *new_sess;
777 SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
778 err = handle_auth_request(sess, pool);
781 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
782 sess->url = apr_pstrdup(sess->pool, url);
785 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
788 /* Servers before 1.4 doesn't support this command; try to reconnect
790 svn_error_clear(err);
791 /* Create a new subpool of the RA session pool. */
792 sess_pool = svn_pool_create(ra_session->pool);
793 err = parse_url(url, &uri, sess_pool);
795 err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
796 sess->callbacks, sess->callbacks_baton, sess_pool);
797 /* We destroy the new session pool on error, since it is allocated in
798 the main session pool. */
801 svn_pool_destroy(sess_pool);
805 /* We have a new connection, assign it and destroy the old. */
806 ra_session->priv = new_sess;
807 svn_pool_destroy(sess->pool);
812 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
813 const char **url, apr_pool_t *pool)
815 svn_ra_svn__session_baton_t *sess = session->priv;
816 *url = apr_pstrdup(pool, sess->url);
820 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
821 svn_revnum_t *rev, apr_pool_t *pool)
823 svn_ra_svn__session_baton_t *sess_baton = session->priv;
824 svn_ra_svn_conn_t *conn = sess_baton->conn;
826 SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
827 SVN_ERR(handle_auth_request(sess_baton, pool));
828 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
832 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
833 svn_revnum_t *rev, apr_time_t tm,
836 svn_ra_svn__session_baton_t *sess_baton = session->priv;
837 svn_ra_svn_conn_t *conn = sess_baton->conn;
839 SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
840 SVN_ERR(handle_auth_request(sess_baton, pool));
841 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
845 /* Forward declaration. */
846 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
848 const char *capability,
851 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
853 const svn_string_t *const *old_value_p,
854 const svn_string_t *value,
857 svn_ra_svn__session_baton_t *sess_baton = session->priv;
858 svn_ra_svn_conn_t *conn = sess_baton->conn;
859 svn_boolean_t dont_care;
860 const svn_string_t *old_value;
861 svn_boolean_t has_atomic_revprops;
863 SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
864 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
869 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
870 SVN_ERR_ASSERT(has_atomic_revprops);
873 old_value = *old_value_p;
881 if (has_atomic_revprops)
882 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
886 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
889 SVN_ERR(handle_auth_request(sess_baton, pool));
890 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
894 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
897 svn_ra_svn__session_baton_t *sess_baton = session->priv;
898 svn_ra_svn_conn_t *conn = sess_baton->conn;
904 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
907 svn_ra_svn__session_baton_t *sess_baton = session->priv;
908 svn_ra_svn_conn_t *conn = sess_baton->conn;
910 if (!conn->repos_root)
911 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
912 _("Server did not send repository root"));
913 *url = conn->repos_root;
917 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
918 apr_hash_t **props, apr_pool_t *pool)
920 svn_ra_svn__session_baton_t *sess_baton = session->priv;
921 svn_ra_svn_conn_t *conn = sess_baton->conn;
922 apr_array_header_t *proplist;
924 SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
925 SVN_ERR(handle_auth_request(sess_baton, pool));
926 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
927 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
931 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
933 svn_string_t **value, apr_pool_t *pool)
935 svn_ra_svn__session_baton_t *sess_baton = session->priv;
936 svn_ra_svn_conn_t *conn = sess_baton->conn;
938 SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
939 SVN_ERR(handle_auth_request(sess_baton, pool));
940 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
944 static svn_error_t *ra_svn_end_commit(void *baton)
946 ra_svn_commit_callback_baton_t *ccb = baton;
947 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
949 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
950 SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
952 &(commit_info->revision),
953 &(commit_info->date),
954 &(commit_info->author),
955 &(commit_info->post_commit_err)));
958 SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
963 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
964 const svn_delta_editor_t **editor,
966 apr_hash_t *revprop_table,
967 svn_commit_callback2_t callback,
968 void *callback_baton,
969 apr_hash_t *lock_tokens,
970 svn_boolean_t keep_locks,
973 svn_ra_svn__session_baton_t *sess_baton = session->priv;
974 svn_ra_svn_conn_t *conn = sess_baton->conn;
975 ra_svn_commit_callback_baton_t *ccb;
976 apr_hash_index_t *hi;
977 apr_pool_t *iterpool;
978 const svn_string_t *log_msg = svn_hash_gets(revprop_table,
979 SVN_PROP_REVISION_LOG);
981 if (log_msg == NULL &&
982 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
984 return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
985 _("ra_svn does not support not specifying "
986 "a log message with pre-1.5 servers; "
987 "consider passing an empty one, or upgrading "
990 else if (log_msg == NULL)
991 /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument
992 to the 'commit' protocol command is non-optional; on the server side,
993 only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The
994 "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
995 will have a NULL log message (not just "", really NULL).
997 svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
998 present; this was elevated to a protocol promise in r1498550 (and
999 later documented in this comment) in order to fix the segmentation
1000 fault bug described in the log message of r1498550.*/
1001 log_msg = svn_string_create("", pool);
1003 /* If we're sending revprops other than svn:log, make sure the server won't
1004 silently ignore them. */
1005 if (apr_hash_count(revprop_table) > 1 &&
1006 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1007 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1008 _("Server doesn't support setting arbitrary "
1009 "revision properties during commit"));
1011 /* If the server supports ephemeral txnprops, add the one that
1012 reports the client's version level string. */
1013 if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1014 svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1016 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1017 svn_string_create(SVN_VER_NUMBER, pool));
1018 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1019 svn_string_create(sess_baton->useragent, pool));
1022 /* Tell the server we're starting the commit.
1023 Send log message here for backwards compatibility with servers
1025 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1029 iterpool = svn_pool_create(pool);
1030 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1034 const char *path, *token;
1036 svn_pool_clear(iterpool);
1037 apr_hash_this(hi, &key, NULL, &val);
1040 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1042 svn_pool_destroy(iterpool);
1044 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1045 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1046 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1047 SVN_ERR(handle_auth_request(sess_baton, pool));
1048 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1050 /* Remember a few arguments for when the commit is over. */
1051 ccb = apr_palloc(pool, sizeof(*ccb));
1052 ccb->sess_baton = sess_baton;
1054 ccb->new_rev = NULL;
1055 ccb->callback = callback;
1056 ccb->callback_baton = callback_baton;
1058 /* Fetch an editor for the caller to drive. The editor will call
1059 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1060 * in the new_rev, committed_date, and committed_author values. */
1061 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1062 ra_svn_end_commit, ccb);
1063 return SVN_NO_ERROR;
1066 /* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1067 const char * repos relative paths and properties for those paths, storing
1068 the result as an array of svn_prop_inherited_item_t *items. */
1069 static svn_error_t *
1070 parse_iproplist(apr_array_header_t **inherited_props,
1071 const apr_array_header_t *iproplist,
1072 svn_ra_session_t *session,
1073 apr_pool_t *result_pool,
1074 apr_pool_t *scratch_pool)
1078 const char *repos_root_url;
1079 apr_pool_t *iterpool;
1081 if (iproplist == NULL)
1083 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1084 capability we shouldn't be asking for inherited props, but if we
1085 did and the server sent back nothing then we'll want to handle
1087 *inherited_props = NULL;
1088 return SVN_NO_ERROR;
1091 SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
1093 *inherited_props = apr_array_make(
1094 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1096 iterpool = svn_pool_create(scratch_pool);
1098 for (i = 0; i < iproplist->nelts; i++)
1100 apr_array_header_t *iprop_list;
1101 char *parent_rel_path;
1103 apr_hash_index_t *hi;
1104 svn_prop_inherited_item_t *new_iprop =
1105 apr_palloc(result_pool, sizeof(*new_iprop));
1106 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1108 if (elt->kind != SVN_RA_SVN_LIST)
1109 return svn_error_create(
1110 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1111 _("Inherited proplist element not a list"));
1113 svn_pool_clear(iterpool);
1115 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1116 &parent_rel_path, &iprop_list));
1117 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1118 new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
1121 new_iprop->prop_hash = apr_hash_make(result_pool);
1122 for (hi = apr_hash_first(iterpool, iprops);
1124 hi = apr_hash_next(hi))
1126 const char *name = svn__apr_hash_index_key(hi);
1127 svn_string_t *value = svn__apr_hash_index_val(hi);
1128 svn_hash_sets(new_iprop->prop_hash,
1129 apr_pstrdup(result_pool, name),
1130 svn_string_dup(value, result_pool));
1132 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1135 svn_pool_destroy(iterpool);
1136 return SVN_NO_ERROR;
1139 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1140 svn_revnum_t rev, svn_stream_t *stream,
1141 svn_revnum_t *fetched_rev,
1145 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1146 svn_ra_svn_conn_t *conn = sess_baton->conn;
1147 apr_array_header_t *proplist;
1148 const char *expected_digest;
1149 svn_checksum_t *expected_checksum = NULL;
1150 svn_checksum_ctx_t *checksum_ctx;
1151 apr_pool_t *iterpool;
1153 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1154 (props != NULL), (stream != NULL)));
1155 SVN_ERR(handle_auth_request(sess_baton, pool));
1156 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1163 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1165 /* We're done if the contents weren't wanted. */
1167 return SVN_NO_ERROR;
1169 if (expected_digest)
1171 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1172 expected_digest, pool));
1173 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1176 /* Read the file's contents. */
1177 iterpool = svn_pool_create(pool);
1180 svn_ra_svn_item_t *item;
1182 svn_pool_clear(iterpool);
1183 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1184 if (item->kind != SVN_RA_SVN_STRING)
1185 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1186 _("Non-string as part of file contents"));
1187 if (item->u.string->len == 0)
1190 if (expected_checksum)
1191 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1192 item->u.string->len));
1194 SVN_ERR(svn_stream_write(stream, item->u.string->data,
1195 &item->u.string->len));
1197 svn_pool_destroy(iterpool);
1199 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1201 if (expected_checksum)
1203 svn_checksum_t *checksum;
1205 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1206 if (!svn_checksum_match(checksum, expected_checksum))
1207 return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1208 _("Checksum mismatch for '%s'"),
1212 return SVN_NO_ERROR;
1215 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1216 apr_hash_t **dirents,
1217 svn_revnum_t *fetched_rev,
1221 apr_uint32_t dirent_fields,
1224 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1225 svn_ra_svn_conn_t *conn = sess_baton->conn;
1226 apr_array_header_t *proplist, *dirlist;
1229 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1230 rev, (props != NULL), (dirents != NULL)));
1231 if (dirent_fields & SVN_DIRENT_KIND)
1232 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1233 if (dirent_fields & SVN_DIRENT_SIZE)
1234 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1235 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1236 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1237 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1238 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1239 if (dirent_fields & SVN_DIRENT_TIME)
1240 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1241 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1242 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1244 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1246 SVN_ERR(handle_auth_request(sess_baton, pool));
1247 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1253 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1255 /* We're done if dirents aren't wanted. */
1257 return SVN_NO_ERROR;
1259 /* Interpret the directory list. */
1260 *dirents = apr_hash_make(pool);
1261 for (i = 0; i < dirlist->nelts; i++)
1263 const char *name, *kind, *cdate, *cauthor;
1264 svn_boolean_t has_props;
1265 svn_dirent_t *dirent;
1268 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1270 if (elt->kind != SVN_RA_SVN_LIST)
1271 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1272 _("Dirlist element not a list"));
1273 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1274 &name, &kind, &size, &has_props,
1275 &crev, &cdate, &cauthor));
1276 name = svn_relpath_canonicalize(name, pool);
1277 dirent = svn_dirent_create(pool);
1278 dirent->kind = svn_node_kind_from_word(kind);
1279 dirent->size = size;/* FIXME: svn_filesize_t */
1280 dirent->has_props = has_props;
1281 dirent->created_rev = crev;
1282 /* NOTE: the tuple's format string says CDATE may be NULL. But this
1283 function does not allow that. The server has always sent us some
1284 random date, however, so this just happens to work. But let's
1285 be wary of servers that are (improperly) fixed to send NULL.
1287 Note: they should NOT be "fixed" to send NULL, as that would break
1288 any older clients which received that NULL. But we may as well
1289 be defensive against a malicous server. */
1293 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1294 dirent->last_author = cauthor;
1295 svn_hash_sets(*dirents, name, dirent);
1298 return SVN_NO_ERROR;
1301 /* Converts a apr_uint64_t with values TRUE, FALSE or
1302 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1303 to a svn_tristate_t */
1304 static svn_tristate_t
1305 optbool_to_tristate(apr_uint64_t v)
1307 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
1308 return svn_tristate_true;
1310 return svn_tristate_false;
1312 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1315 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1316 server, which defaults to youngest. */
1317 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1318 svn_mergeinfo_catalog_t *catalog,
1319 const apr_array_header_t *paths,
1320 svn_revnum_t revision,
1321 svn_mergeinfo_inheritance_t inherit,
1322 svn_boolean_t include_descendants,
1325 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1326 svn_ra_svn_conn_t *conn = sess_baton->conn;
1328 apr_array_header_t *mergeinfo_tuple;
1329 svn_ra_svn_item_t *elt;
1332 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1333 for (i = 0; i < paths->nelts; i++)
1335 path = APR_ARRAY_IDX(paths, i, const char *);
1336 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1338 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1339 svn_inheritance_to_word(inherit),
1340 include_descendants));
1342 SVN_ERR(handle_auth_request(sess_baton, pool));
1343 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1346 if (mergeinfo_tuple->nelts > 0)
1348 *catalog = apr_hash_make(pool);
1349 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1351 svn_mergeinfo_t for_path;
1352 const char *to_parse;
1354 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1355 if (elt->kind != SVN_RA_SVN_LIST)
1356 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1357 _("Mergeinfo element is not a list"));
1358 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1360 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1361 /* Correct for naughty servers that send "relative" paths
1362 with leading slashes! */
1363 svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1367 return SVN_NO_ERROR;
1370 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1371 const svn_ra_reporter3_t **reporter,
1372 void **report_baton, svn_revnum_t rev,
1373 const char *target, svn_depth_t depth,
1374 svn_boolean_t send_copyfrom_args,
1375 svn_boolean_t ignore_ancestry,
1376 const svn_delta_editor_t *update_editor,
1379 apr_pool_t *scratch_pool)
1381 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1382 svn_ra_svn_conn_t *conn = sess_baton->conn;
1383 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1385 /* Tell the server we want to start an update. */
1386 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1387 depth, send_copyfrom_args,
1389 SVN_ERR(handle_auth_request(sess_baton, pool));
1391 /* Fetch a reporter for the caller to drive. The reporter will drive
1392 * update_editor upon finish_report(). */
1393 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1394 target, depth, reporter, report_baton));
1395 return SVN_NO_ERROR;
1398 static svn_error_t *
1399 ra_svn_switch(svn_ra_session_t *session,
1400 const svn_ra_reporter3_t **reporter,
1401 void **report_baton, svn_revnum_t rev,
1402 const char *target, svn_depth_t depth,
1403 const char *switch_url,
1404 svn_boolean_t send_copyfrom_args,
1405 svn_boolean_t ignore_ancestry,
1406 const svn_delta_editor_t *update_editor,
1408 apr_pool_t *result_pool,
1409 apr_pool_t *scratch_pool)
1411 apr_pool_t *pool = result_pool;
1412 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1413 svn_ra_svn_conn_t *conn = sess_baton->conn;
1414 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1416 /* Tell the server we want to start a switch. */
1417 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1419 send_copyfrom_args, ignore_ancestry));
1420 SVN_ERR(handle_auth_request(sess_baton, pool));
1422 /* Fetch a reporter for the caller to drive. The reporter will drive
1423 * update_editor upon finish_report(). */
1424 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1425 target, depth, reporter, report_baton));
1426 return SVN_NO_ERROR;
1429 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1430 const svn_ra_reporter3_t **reporter,
1431 void **report_baton,
1432 const char *target, svn_revnum_t rev,
1434 const svn_delta_editor_t *status_editor,
1435 void *status_baton, apr_pool_t *pool)
1437 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1438 svn_ra_svn_conn_t *conn = sess_baton->conn;
1439 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1441 /* Tell the server we want to start a status operation. */
1442 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1444 SVN_ERR(handle_auth_request(sess_baton, pool));
1446 /* Fetch a reporter for the caller to drive. The reporter will drive
1447 * status_editor upon finish_report(). */
1448 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1449 target, depth, reporter, report_baton));
1450 return SVN_NO_ERROR;
1453 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1454 const svn_ra_reporter3_t **reporter,
1455 void **report_baton,
1456 svn_revnum_t rev, const char *target,
1458 svn_boolean_t ignore_ancestry,
1459 svn_boolean_t text_deltas,
1460 const char *versus_url,
1461 const svn_delta_editor_t *diff_editor,
1462 void *diff_baton, apr_pool_t *pool)
1464 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1465 svn_ra_svn_conn_t *conn = sess_baton->conn;
1466 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1468 /* Tell the server we want to start a diff. */
1469 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1470 ignore_ancestry, versus_url,
1471 text_deltas, depth));
1472 SVN_ERR(handle_auth_request(sess_baton, pool));
1474 /* Fetch a reporter for the caller to drive. The reporter will drive
1475 * diff_editor upon finish_report(). */
1476 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1477 target, depth, reporter, report_baton));
1478 return SVN_NO_ERROR;
1482 static svn_error_t *
1483 perform_ra_svn_log(svn_error_t **outer_error,
1484 svn_ra_session_t *session,
1485 const apr_array_header_t *paths,
1486 svn_revnum_t start, svn_revnum_t end,
1488 svn_boolean_t discover_changed_paths,
1489 svn_boolean_t strict_node_history,
1490 svn_boolean_t include_merged_revisions,
1491 const apr_array_header_t *revprops,
1492 svn_log_entry_receiver_t receiver,
1493 void *receiver_baton,
1496 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1497 svn_ra_svn_conn_t *conn = sess_baton->conn;
1498 apr_pool_t *iterpool;
1503 svn_boolean_t want_custom_revprops;
1505 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1508 for (i = 0; i < paths->nelts; i++)
1510 path = APR_ARRAY_IDX(paths, i, const char *);
1511 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1514 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1515 discover_changed_paths, strict_node_history,
1516 (apr_uint64_t) limit,
1517 include_merged_revisions));
1520 want_custom_revprops = FALSE;
1521 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1522 for (i = 0; i < revprops->nelts; i++)
1524 name = APR_ARRAY_IDX(revprops, i, char *);
1525 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1526 if (!want_custom_revprops
1527 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
1528 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
1529 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
1530 want_custom_revprops = TRUE;
1532 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1536 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1537 want_custom_revprops = TRUE;
1540 SVN_ERR(handle_auth_request(sess_baton, pool));
1542 /* Read the log messages. */
1543 iterpool = svn_pool_create(pool);
1546 apr_uint64_t has_children_param, invalid_revnum_param;
1547 apr_uint64_t has_subtractive_merge_param;
1548 svn_string_t *author, *date, *message;
1549 apr_array_header_t *cplist, *rplist;
1550 svn_log_entry_t *log_entry;
1551 svn_boolean_t has_children;
1552 svn_boolean_t subtractive_merge = FALSE;
1553 apr_uint64_t revprop_count;
1554 svn_ra_svn_item_t *item;
1559 svn_pool_clear(iterpool);
1560 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1561 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1563 if (item->kind != SVN_RA_SVN_LIST)
1564 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1565 _("Log entry not a list"));
1566 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1567 "lr(?s)(?s)(?s)?BBnl?B",
1568 &cplist, &rev, &author, &date,
1569 &message, &has_children_param,
1570 &invalid_revnum_param,
1571 &revprop_count, &rplist,
1572 &has_subtractive_merge_param));
1573 if (want_custom_revprops && rplist == NULL)
1575 /* Caller asked for custom revprops, but server is too old. */
1576 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1577 _("Server does not support custom revprops"
1581 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1582 has_children = FALSE;
1584 has_children = (svn_boolean_t) has_children_param;
1586 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1587 subtractive_merge = FALSE;
1589 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1591 /* Because the svn protocol won't let us send an invalid revnum, we have
1592 to recover that fact using the extra parameter. */
1593 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1594 && invalid_revnum_param)
1595 rev = SVN_INVALID_REVNUM;
1597 if (cplist->nelts > 0)
1599 /* Interpret the changed-paths list. */
1600 cphash = apr_hash_make(iterpool);
1601 for (i = 0; i < cplist->nelts; i++)
1603 svn_log_changed_path2_t *change;
1604 const char *copy_path, *action, *cpath, *kind_str;
1605 apr_uint64_t text_mods, prop_mods;
1606 svn_revnum_t copy_rev;
1607 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1610 if (elt->kind != SVN_RA_SVN_LIST)
1611 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1612 _("Changed-path entry not a list"));
1613 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool,
1615 &cpath, &action, ©_path,
1616 ©_rev, &kind_str,
1617 &text_mods, &prop_mods));
1618 cpath = svn_fspath__canonicalize(cpath, iterpool);
1620 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1621 change = svn_log_changed_path2_create(iterpool);
1622 change->action = *action;
1623 change->copyfrom_path = copy_path;
1624 change->copyfrom_rev = copy_rev;
1625 change->node_kind = svn_node_kind_from_word(kind_str);
1626 change->text_modified = optbool_to_tristate(text_mods);
1627 change->props_modified = optbool_to_tristate(prop_mods);
1628 svn_hash_sets(cphash, cpath, change);
1635 if (! (limit && (nest_level == 0) && (++nreceived > limit))
1639 log_entry = svn_log_entry_create(iterpool);
1641 log_entry->changed_paths = cphash;
1642 log_entry->changed_paths2 = cphash;
1643 log_entry->revision = rev;
1644 log_entry->has_children = has_children;
1645 log_entry->subtractive_merge = subtractive_merge;
1647 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1648 &log_entry->revprops));
1649 if (log_entry->revprops == NULL)
1650 log_entry->revprops = apr_hash_make(iterpool);
1651 if (revprops == NULL)
1653 /* Caller requested all revprops; set author/date/log. */
1655 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1658 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1661 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG,
1666 /* Caller requested some; maybe set author/date/log. */
1667 for (i = 0; i < revprops->nelts; i++)
1669 name = APR_ARRAY_IDX(revprops, i, char *);
1670 if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1671 svn_hash_sets(log_entry->revprops,
1672 SVN_PROP_REVISION_AUTHOR, author);
1673 if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1674 svn_hash_sets(log_entry->revprops,
1675 SVN_PROP_REVISION_DATE, date);
1676 if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1677 svn_hash_sets(log_entry->revprops,
1678 SVN_PROP_REVISION_LOG, message);
1681 err = receiver(receiver_baton, log_entry, iterpool);
1682 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1684 *outer_error = svn_error_trace(
1685 svn_error_compose_create(*outer_error, err));
1690 if (log_entry->has_children)
1694 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1696 SVN_ERR_ASSERT(nest_level);
1701 svn_pool_destroy(iterpool);
1703 /* Read the response. */
1704 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1707 static svn_error_t *
1708 ra_svn_log(svn_ra_session_t *session,
1709 const apr_array_header_t *paths,
1710 svn_revnum_t start, svn_revnum_t end,
1712 svn_boolean_t discover_changed_paths,
1713 svn_boolean_t strict_node_history,
1714 svn_boolean_t include_merged_revisions,
1715 const apr_array_header_t *revprops,
1716 svn_log_entry_receiver_t receiver,
1717 void *receiver_baton, apr_pool_t *pool)
1719 svn_error_t *outer_error = NULL;
1722 err = svn_error_trace(perform_ra_svn_log(&outer_error,
1726 discover_changed_paths,
1727 strict_node_history,
1728 include_merged_revisions,
1730 receiver, receiver_baton,
1732 return svn_error_trace(
1733 svn_error_compose_create(outer_error,
1739 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1740 const char *path, svn_revnum_t rev,
1741 svn_node_kind_t *kind, apr_pool_t *pool)
1743 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1744 svn_ra_svn_conn_t *conn = sess_baton->conn;
1745 const char *kind_word;
1747 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1748 SVN_ERR(handle_auth_request(sess_baton, pool));
1749 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1750 *kind = svn_node_kind_from_word(kind_word);
1751 return SVN_NO_ERROR;
1755 /* If ERR is a command not supported error, wrap it in a
1756 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1757 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1760 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1761 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1767 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1768 const char *path, svn_revnum_t rev,
1769 svn_dirent_t **dirent, apr_pool_t *pool)
1771 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1772 svn_ra_svn_conn_t *conn = sess_baton->conn;
1773 apr_array_header_t *list = NULL;
1774 svn_dirent_t *the_dirent;
1776 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1777 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1778 N_("'stat' not implemented")));
1779 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1787 const char *kind, *cdate, *cauthor;
1788 svn_boolean_t has_props;
1792 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1793 &kind, &size, &has_props,
1794 &crev, &cdate, &cauthor));
1796 the_dirent = svn_dirent_create(pool);
1797 the_dirent->kind = svn_node_kind_from_word(kind);
1798 the_dirent->size = size;/* FIXME: svn_filesize_t */
1799 the_dirent->has_props = has_props;
1800 the_dirent->created_rev = crev;
1801 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1802 the_dirent->last_author = cauthor;
1804 *dirent = the_dirent;
1807 return SVN_NO_ERROR;
1811 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1812 apr_hash_t **locations,
1814 svn_revnum_t peg_revision,
1815 const apr_array_header_t *location_revisions,
1818 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1819 svn_ra_svn_conn_t *conn = sess_baton->conn;
1820 svn_revnum_t revision;
1821 svn_boolean_t is_done;
1824 /* Transmit the parameters. */
1825 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1826 "get-locations", path, peg_revision));
1827 for (i = 0; i < location_revisions->nelts; i++)
1829 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1830 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1833 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1835 /* Servers before 1.1 don't support this command. Check for this here. */
1836 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1837 N_("'get-locations' not implemented")));
1839 /* Read the hash items. */
1841 *locations = apr_hash_make(pool);
1844 svn_ra_svn_item_t *item;
1845 const char *ret_path;
1847 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1848 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1850 else if (item->kind != SVN_RA_SVN_LIST)
1851 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1852 _("Location entry not a list"));
1855 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1856 &revision, &ret_path));
1857 ret_path = svn_fspath__canonicalize(ret_path, pool);
1858 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1860 sizeof(revision), ret_path);
1864 /* Read the response. This is so the server would have a chance to
1865 * report an error. */
1866 return svn_ra_svn__read_cmd_response(conn, pool, "");
1869 static svn_error_t *
1870 ra_svn_get_location_segments(svn_ra_session_t *session,
1872 svn_revnum_t peg_revision,
1873 svn_revnum_t start_rev,
1874 svn_revnum_t end_rev,
1875 svn_location_segment_receiver_t receiver,
1876 void *receiver_baton,
1879 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1880 svn_ra_svn_conn_t *conn = sess_baton->conn;
1881 svn_boolean_t is_done;
1882 apr_pool_t *iterpool = svn_pool_create(pool);
1884 /* Transmit the parameters. */
1885 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
1886 "get-location-segments",
1887 path, peg_revision, start_rev, end_rev));
1889 /* Servers before 1.5 don't support this command. Check for this here. */
1890 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1891 N_("'get-location-segments'"
1892 " not implemented")));
1894 /* Parse the response. */
1898 svn_revnum_t range_start, range_end;
1899 svn_ra_svn_item_t *item;
1900 const char *ret_path;
1902 svn_pool_clear(iterpool);
1903 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1904 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1906 else if (item->kind != SVN_RA_SVN_LIST)
1907 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1908 _("Location segment entry not a list"));
1911 svn_location_segment_t *segment = apr_pcalloc(iterpool,
1913 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
1914 &range_start, &range_end, &ret_path));
1915 if (! (SVN_IS_VALID_REVNUM(range_start)
1916 && SVN_IS_VALID_REVNUM(range_end)))
1917 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1918 _("Expected valid revision range"));
1920 ret_path = svn_relpath_canonicalize(ret_path, iterpool);
1921 segment->path = ret_path;
1922 segment->range_start = range_start;
1923 segment->range_end = range_end;
1924 SVN_ERR(receiver(segment, receiver_baton, iterpool));
1927 svn_pool_destroy(iterpool);
1929 /* Read the response. This is so the server would have a chance to
1930 * report an error. */
1931 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1933 return SVN_NO_ERROR;
1936 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
1938 svn_revnum_t start, svn_revnum_t end,
1939 svn_boolean_t include_merged_revisions,
1940 svn_file_rev_handler_t handler,
1941 void *handler_baton, apr_pool_t *pool)
1943 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1944 apr_pool_t *rev_pool, *chunk_pool;
1945 svn_boolean_t has_txdelta;
1946 svn_boolean_t had_revision = FALSE;
1948 /* One sub-pool for each revision and one for each txdelta chunk.
1949 Note that the rev_pool must live during the following txdelta. */
1950 rev_pool = svn_pool_create(pool);
1951 chunk_pool = svn_pool_create(pool);
1953 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
1955 include_merged_revisions));
1957 /* Servers before 1.1 don't support this command. Check for this here. */
1958 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1959 N_("'get-file-revs' not implemented")));
1963 apr_array_header_t *rev_proplist, *proplist;
1964 apr_uint64_t merged_rev_param;
1965 apr_array_header_t *props;
1966 svn_ra_svn_item_t *item;
1967 apr_hash_t *rev_props;
1970 svn_boolean_t merged_rev;
1971 svn_txdelta_window_handler_t d_handler;
1974 svn_pool_clear(rev_pool);
1975 svn_pool_clear(chunk_pool);
1976 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
1977 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1979 /* Either we've got a correct revision or we will error out below. */
1980 had_revision = TRUE;
1981 if (item->kind != SVN_RA_SVN_LIST)
1982 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1983 _("Revision entry not a list"));
1985 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
1986 "crll?B", &p, &rev, &rev_proplist,
1987 &proplist, &merged_rev_param));
1988 p = svn_fspath__canonicalize(p, rev_pool);
1989 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
1990 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
1991 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1994 merged_rev = (svn_boolean_t) merged_rev_param;
1996 /* Get the first delta chunk so we know if there is a delta. */
1997 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
1998 if (item->kind != SVN_RA_SVN_STRING)
1999 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2000 _("Text delta chunk not a string"));
2001 has_txdelta = item->u.string->len > 0;
2003 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2004 has_txdelta ? &d_handler : NULL, &d_baton,
2007 /* Process the text delta if any. */
2010 svn_stream_t *stream;
2013 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2017 while (item->u.string->len > 0)
2021 size = item->u.string->len;
2023 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2024 svn_pool_clear(chunk_pool);
2026 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2028 if (item->kind != SVN_RA_SVN_STRING)
2029 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2030 _("Text delta chunk not a string"));
2033 SVN_ERR(svn_stream_close(stream));
2037 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2039 /* Return error if we didn't get any revisions. */
2041 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2042 _("The get-file-revs command didn't return "
2045 svn_pool_destroy(chunk_pool);
2046 svn_pool_destroy(rev_pool);
2048 return SVN_NO_ERROR;
2051 /* For each path in PATH_REVS, send a 'lock' command to the server.
2052 Used with 1.2.x series servers which support locking, but of only
2053 one path at a time. ra_svn_lock(), which supports 'lock-many'
2054 is now the default. See svn_ra_lock() docstring for interface details. */
2055 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2056 apr_hash_t *path_revs,
2057 const char *comment,
2058 svn_boolean_t steal_lock,
2059 svn_ra_lock_callback_t lock_func,
2063 svn_ra_svn__session_baton_t *sess = session->priv;
2064 svn_ra_svn_conn_t* conn = sess->conn;
2065 apr_array_header_t *list;
2066 apr_hash_index_t *hi;
2067 apr_pool_t *iterpool = svn_pool_create(pool);
2069 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2075 svn_revnum_t *revnum;
2076 svn_error_t *err, *callback_err = NULL;
2078 svn_pool_clear(iterpool);
2080 apr_hash_this(hi, &key, NULL, &val);
2084 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2085 steal_lock, *revnum));
2087 /* Servers before 1.2 doesn't support locking. Check this here. */
2088 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2089 N_("Server doesn't support "
2090 "the lock command")));
2092 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2095 SVN_ERR(parse_lock(list, iterpool, &lock));
2097 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2101 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2104 svn_error_clear(err);
2107 return callback_err;
2110 svn_pool_destroy(iterpool);
2112 return SVN_NO_ERROR;
2115 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2116 Used with 1.2.x series servers which support unlocking, but of only
2117 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
2118 now the default. See svn_ra_unlock() docstring for interface details. */
2119 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2120 apr_hash_t *path_tokens,
2121 svn_boolean_t break_lock,
2122 svn_ra_lock_callback_t lock_func,
2126 svn_ra_svn__session_baton_t *sess = session->priv;
2127 svn_ra_svn_conn_t* conn = sess->conn;
2128 apr_hash_index_t *hi;
2129 apr_pool_t *iterpool = svn_pool_create(pool);
2131 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2137 svn_error_t *err, *callback_err = NULL;
2139 svn_pool_clear(iterpool);
2141 apr_hash_this(hi, &key, NULL, &val);
2143 if (strcmp(val, "") != 0)
2148 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2151 /* Servers before 1.2 don't support locking. Check this here. */
2152 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2153 N_("Server doesn't support the unlock "
2156 err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2158 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2162 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2164 svn_error_clear(err);
2167 return callback_err;
2170 svn_pool_destroy(iterpool);
2172 return SVN_NO_ERROR;
2175 /* Tell the server to lock all paths in PATH_REVS.
2176 See svn_ra_lock() for interface details. */
2177 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2178 apr_hash_t *path_revs,
2179 const char *comment,
2180 svn_boolean_t steal_lock,
2181 svn_ra_lock_callback_t lock_func,
2185 svn_ra_svn__session_baton_t *sess = session->priv;
2186 svn_ra_svn_conn_t *conn = sess->conn;
2187 apr_hash_index_t *hi;
2189 apr_pool_t *iterpool = svn_pool_create(pool);
2191 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2192 comment, steal_lock));
2194 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2199 svn_revnum_t *revnum;
2201 svn_pool_clear(iterpool);
2202 apr_hash_this(hi, &key, NULL, &val);
2206 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2209 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2211 err = handle_auth_request(sess, pool);
2213 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2215 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2217 svn_error_clear(err);
2218 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2219 lock_func, lock_baton, pool);
2225 /* Loop over responses to get lock information. */
2226 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2228 svn_ra_svn_item_t *elt;
2231 svn_error_t *callback_err;
2234 apr_array_header_t *list;
2236 apr_hash_this(hi, &key, NULL, NULL);
2239 svn_pool_clear(iterpool);
2240 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2242 /* The server might have encountered some sort of fatal error in
2243 the middle of the request list. If this happens, it will
2244 transmit "done" to end the lock-info early, and then the
2245 overall command response will talk about the fatal error. */
2246 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2249 if (elt->kind != SVN_RA_SVN_LIST)
2250 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2251 _("Lock response not a list"));
2253 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2256 if (strcmp(status, "failure") == 0)
2257 err = svn_ra_svn__handle_failure_status(list, iterpool);
2258 else if (strcmp(status, "success") == 0)
2260 SVN_ERR(parse_lock(list, iterpool, &lock));
2264 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2265 _("Unknown status for lock command"));
2268 callback_err = lock_func(lock_baton, path, TRUE,
2272 callback_err = SVN_NO_ERROR;
2274 svn_error_clear(err);
2277 return callback_err;
2280 /* If we didn't break early above, and the whole hash was traversed,
2281 read the final "done" from the server. */
2284 svn_ra_svn_item_t *elt;
2286 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2287 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2288 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2289 _("Didn't receive end marker for lock "
2293 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2295 svn_pool_destroy(iterpool);
2297 return SVN_NO_ERROR;
2300 /* Tell the server to unlock all paths in PATH_TOKENS.
2301 See svn_ra_unlock() for interface details. */
2302 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2303 apr_hash_t *path_tokens,
2304 svn_boolean_t break_lock,
2305 svn_ra_lock_callback_t lock_func,
2309 svn_ra_svn__session_baton_t *sess = session->priv;
2310 svn_ra_svn_conn_t *conn = sess->conn;
2311 apr_hash_index_t *hi;
2312 apr_pool_t *iterpool = svn_pool_create(pool);
2316 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2319 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2325 svn_pool_clear(iterpool);
2326 apr_hash_this(hi, &key, NULL, &val);
2329 if (strcmp(val, "") != 0)
2334 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2337 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2339 err = handle_auth_request(sess, pool);
2341 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2344 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2346 svn_error_clear(err);
2347 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2354 /* Loop over responses to unlock files. */
2355 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2357 svn_ra_svn_item_t *elt;
2359 svn_error_t *callback_err;
2361 apr_array_header_t *list;
2363 svn_pool_clear(iterpool);
2365 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2367 /* The server might have encountered some sort of fatal error in
2368 the middle of the request list. If this happens, it will
2369 transmit "done" to end the lock-info early, and then the
2370 overall command response will talk about the fatal error. */
2371 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2374 apr_hash_this(hi, &key, NULL, NULL);
2377 if (elt->kind != SVN_RA_SVN_LIST)
2378 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2379 _("Unlock response not a list"));
2381 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2384 if (strcmp(status, "failure") == 0)
2385 err = svn_ra_svn__handle_failure_status(list, iterpool);
2386 else if (strcmp(status, "success") == 0)
2388 SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2392 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2393 _("Unknown status for unlock command"));
2396 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2399 callback_err = SVN_NO_ERROR;
2401 svn_error_clear(err);
2404 return callback_err;
2407 /* If we didn't break early above, and the whole hash was traversed,
2408 read the final "done" from the server. */
2411 svn_ra_svn_item_t *elt;
2413 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2414 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2415 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2416 _("Didn't receive end marker for unlock "
2420 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2422 svn_pool_destroy(iterpool);
2424 return SVN_NO_ERROR;
2427 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2432 svn_ra_svn__session_baton_t *sess = session->priv;
2433 svn_ra_svn_conn_t* conn = sess->conn;
2434 apr_array_header_t *list;
2436 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2438 /* Servers before 1.2 doesn't support locking. Check this here. */
2439 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2440 N_("Server doesn't support the get-lock "
2443 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2445 SVN_ERR(parse_lock(list, pool, lock));
2449 return SVN_NO_ERROR;
2452 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2453 to prevent a dependency cycle. */
2454 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2455 const char **rel_path,
2459 const char *root_url;
2461 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2462 *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2464 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2465 _("'%s' isn't a child of repository root "
2468 return SVN_NO_ERROR;
2471 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2477 svn_ra_svn__session_baton_t *sess = session->priv;
2478 svn_ra_svn_conn_t* conn = sess->conn;
2479 apr_array_header_t *list;
2480 const char *full_url, *abs_path;
2483 /* Figure out the repository abspath from PATH. */
2484 full_url = svn_path_url_add_component2(sess->url, path, pool);
2485 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2486 abs_path = svn_fspath__canonicalize(abs_path, pool);
2488 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2490 /* Servers before 1.2 doesn't support locking. Check this here. */
2491 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2492 N_("Server doesn't support the get-lock "
2495 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2497 *locks = apr_hash_make(pool);
2499 for (i = 0; i < list->nelts; ++i)
2502 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2504 if (elt->kind != SVN_RA_SVN_LIST)
2505 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2506 _("Lock element not a list"));
2507 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2509 /* Filter out unwanted paths. Since Subversion only allows
2510 locks on files, we can treat depth=immediates the same as
2511 depth=files for filtering purposes. Meaning, we'll keep
2514 a) its path is the very path we queried, or
2515 b) we've asked for a fully recursive answer, or
2516 c) we've asked for depth=files or depth=immediates, and this
2517 lock is on an immediate child of our query path.
2519 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2521 svn_hash_sets(*locks, lock->path, lock);
2523 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2525 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2526 if (relpath && (svn_path_component_count(relpath) == 1))
2527 svn_hash_sets(*locks, lock->path, lock);
2531 return SVN_NO_ERROR;
2535 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2536 svn_revnum_t revision,
2537 svn_revnum_t low_water_mark,
2538 svn_boolean_t send_deltas,
2539 const svn_delta_editor_t *editor,
2543 svn_ra_svn__session_baton_t *sess = session->priv;
2545 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2546 low_water_mark, send_deltas));
2548 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2549 N_("Server doesn't support the replay "
2552 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2555 return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
2559 static svn_error_t *
2560 ra_svn_replay_range(svn_ra_session_t *session,
2561 svn_revnum_t start_revision,
2562 svn_revnum_t end_revision,
2563 svn_revnum_t low_water_mark,
2564 svn_boolean_t send_deltas,
2565 svn_ra_replay_revstart_callback_t revstart_func,
2566 svn_ra_replay_revfinish_callback_t revfinish_func,
2570 svn_ra_svn__session_baton_t *sess = session->priv;
2571 apr_pool_t *iterpool;
2573 svn_boolean_t drive_aborted = FALSE;
2575 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2576 start_revision, end_revision,
2577 low_water_mark, send_deltas));
2579 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2580 N_("Server doesn't support the "
2581 "replay-range command")));
2583 iterpool = svn_pool_create(pool);
2584 for (rev = start_revision; rev <= end_revision; rev++)
2586 const svn_delta_editor_t *editor;
2588 apr_hash_t *rev_props;
2590 apr_array_header_t *list;
2592 svn_pool_clear(iterpool);
2594 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2595 "wl", &word, &list));
2596 if (strcmp(word, "revprops") != 0)
2597 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2598 _("Expected 'revprops', found '%s'"),
2601 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2603 SVN_ERR(revstart_func(rev, replay_baton,
2604 &editor, &edit_baton,
2607 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2609 &drive_aborted, TRUE));
2610 /* If drive_editor2() aborted the commit, do NOT try to call
2611 revfinish_func and commit the transaction! */
2612 if (drive_aborted) {
2613 svn_pool_destroy(iterpool);
2614 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2615 _("Error while replaying commit"));
2617 SVN_ERR(revfinish_func(rev, replay_baton,
2622 svn_pool_destroy(iterpool);
2624 return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
2628 static svn_error_t *
2629 ra_svn_has_capability(svn_ra_session_t *session,
2631 const char *capability,
2634 svn_ra_svn__session_baton_t *sess = session->priv;
2635 static const char* capabilities[][2] =
2637 /* { ra capability string, svn:// wire capability string} */
2638 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2639 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2640 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2641 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2642 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2643 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2644 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2645 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2646 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2647 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2648 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2650 {NULL, NULL} /* End of list marker */
2656 for (i = 0; capabilities[i][0]; i++)
2658 if (strcmp(capability, capabilities[i][0]) == 0)
2660 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2661 return SVN_NO_ERROR;
2665 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2666 _("Don't know anything about capability '%s'"),
2670 static svn_error_t *
2671 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2673 svn_revnum_t peg_revision,
2674 svn_revnum_t end_revision,
2675 svn_revnum_t *revision_deleted,
2679 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2680 svn_ra_svn_conn_t *conn = sess_baton->conn;
2682 /* Transmit the parameters. */
2683 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2684 peg_revision, end_revision));
2686 /* Servers before 1.6 don't support this command. Check for this here. */
2687 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2688 N_("'get-deleted-rev' not implemented")));
2690 return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted);
2693 static svn_error_t *
2694 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2695 svn_delta_shim_callbacks_t *callbacks)
2697 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2698 svn_ra_svn_conn_t *conn = sess_baton->conn;
2700 conn->shim_callbacks = callbacks;
2702 return SVN_NO_ERROR;
2705 static svn_error_t *
2706 ra_svn_get_inherited_props(svn_ra_session_t *session,
2707 apr_array_header_t **iprops,
2709 svn_revnum_t revision,
2710 apr_pool_t *result_pool,
2711 apr_pool_t *scratch_pool)
2713 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2714 svn_ra_svn_conn_t *conn = sess_baton->conn;
2715 apr_array_header_t *iproplist;
2717 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2719 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2720 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2721 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2724 return SVN_NO_ERROR;
2727 static const svn_ra__vtable_t ra_svn_vtable = {
2729 ra_svn_get_description,
2733 ra_svn_get_session_url,
2734 ra_svn_get_latest_rev,
2735 ra_svn_get_dated_rev,
2736 ra_svn_change_rev_prop,
2737 ra_svn_rev_proplist,
2742 ra_svn_get_mergeinfo,
2751 ra_svn_get_repos_root,
2752 ra_svn_get_locations,
2753 ra_svn_get_location_segments,
2754 ra_svn_get_file_revs,
2760 ra_svn_has_capability,
2761 ra_svn_replay_range,
2762 ra_svn_get_deleted_rev,
2763 ra_svn_register_editor_shim_callbacks,
2764 ra_svn_get_inherited_props
2768 svn_ra_svn__init(const svn_version_t *loader_version,
2769 const svn_ra__vtable_t **vtable,
2772 static const svn_version_checklist_t checklist[] =
2774 { "svn_subr", svn_subr_version },
2775 { "svn_delta", svn_delta_version },
2779 SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2781 /* Simplified version check to make sure we can safely use the
2782 VTABLE parameter. The RA loader does a more exhaustive check. */
2783 if (loader_version->major != SVN_VER_MAJOR)
2785 return svn_error_createf
2786 (SVN_ERR_VERSION_MISMATCH, NULL,
2787 _("Unsupported RA loader version (%d) for ra_svn"),
2788 loader_version->major);
2791 *vtable = &ra_svn_vtable;
2793 #ifdef SVN_HAVE_SASL
2794 SVN_ERR(svn_ra_svn__sasl_init());
2797 return SVN_NO_ERROR;
2800 /* Compatibility wrapper for the 1.1 and before API. */
2801 #define NAME "ra_svn"
2802 #define DESCRIPTION RA_SVN_DESCRIPTION
2803 #define VTBL ra_svn_vtable
2804 #define INITFUNC svn_ra_svn__init
2805 #define COMPAT_INITFUNC svn_ra_svn_init
2806 #include "../libsvn_ra/wrapper_template.h"