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_error_trace(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_P is an output parameter.) */
372 static svn_error_t *find_tunnel_agent(const char *tunnel,
373 const char *hostinfo,
374 const char ***argv_p,
375 apr_hash_t *config, apr_pool_t *pool)
378 const char *val, *var, *cmd;
385 /* Look up the tunnel specification in config. */
386 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
387 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
389 /* We have one predefined tunnel scheme, if it isn't overridden by config. */
390 if (!val && strcmp(tunnel, "ssh") == 0)
392 /* Killing the tunnel agent with SIGTERM leads to unsightly
393 * stderr output from ssh, unless we pass -q.
394 * The "-q" option to ssh is widely supported: all versions of
395 * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
396 * versions have it too. If the user is using some other ssh
397 * implementation that doesn't accept it, they can override it
398 * in the [tunnels] section of the config. */
399 val = "$SVN_SSH ssh -q";
403 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
404 _("Undefined tunnel scheme '%s'"), tunnel);
406 /* If the scheme definition begins with "$varname", it means there
407 * is an environment variable which can override the command. */
411 len = strcspn(val, " ");
412 var = apr_pstrmemdup(pool, val, len);
420 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
421 _("Tunnel scheme %s requires environment "
422 "variable %s to be defined"), tunnel,
429 /* Tokenize the command into a list of arguments. */
430 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
431 if (status != APR_SUCCESS)
432 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
434 /* Calc number of the fixed arguments. */
435 for (n = 0; cmd_argv[n] != NULL; n++)
438 argv = apr_palloc(pool, (n + 4) * sizeof(char *));
440 /* Append the fixed arguments to the result. */
441 for (n = 0; cmd_argv[n] != NULL; n++)
442 argv[n] = cmd_argv[n];
444 argv[n++] = svn_path_uri_decode(hostinfo, pool);
445 argv[n++] = "svnserve";
453 /* This function handles any errors which occur in the child process
454 * created for a tunnel agent. We write the error out as a command
455 * failure; the code in ra_svn_open() to read the server's greeting
456 * will see the error and return it to the caller. */
457 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
460 svn_ra_svn_conn_t *conn;
461 apr_file_t *in_file, *out_file;
462 svn_stream_t *in_stream, *out_stream;
465 if (apr_file_open_stdin(&in_file, pool)
466 || apr_file_open_stdout(&out_file, pool))
469 in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool);
470 out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool);
472 conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream,
473 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
475 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
476 svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
477 svn_error_clear(err);
478 svn_error_clear(svn_ra_svn__flush(conn, pool));
481 /* (Note: *CONN is an output parameter.) */
482 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
487 apr_procattr_t *attr;
490 status = apr_procattr_create(&attr, pool);
491 if (status == APR_SUCCESS)
492 status = apr_procattr_io_set(attr, 1, 1, 0);
493 if (status == APR_SUCCESS)
494 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
495 if (status == APR_SUCCESS)
496 status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
497 proc = apr_palloc(pool, sizeof(*proc));
498 if (status == APR_SUCCESS)
499 status = apr_proc_create(proc, *args, args, NULL, attr, pool);
500 if (status != APR_SUCCESS)
501 return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
502 svn_error_wrap_apr(status,
503 _("Can't create tunnel")), NULL);
505 /* Arrange for the tunnel agent to get a SIGTERM on pool
506 * cleanup. This is a little extreme, but the alternatives
507 * weren't working out.
509 * Closing the pipes and waiting for the process to die
510 * was prone to mysterious hangs which are difficult to
511 * diagnose (e.g. svnserve dumps core due to unrelated bug;
512 * sshd goes into zombie state; ssh connection is never
513 * closed; ssh never terminates).
514 * See also the long dicussion in issue #2580 if you really
515 * want to know various reasons for these problems and
516 * the different opinions on this issue.
518 * On Win32, APR does not support KILL_ONLY_ONCE. It only has
519 * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
520 * KILL_ALWAYS, which immediately calls TerminateProcess().
521 * This instantly kills the tunnel, leaving sshd and svnserve
522 * on a remote machine running indefinitely. These processes
523 * accumulate. The problem is most often seen with a fast client
524 * machine and a modest internet connection, as the tunnel
525 * is killed before being able to gracefully complete the
526 * session. In that case, svn is unusable 100% of the time on
527 * the windows machine. Thus, on Win32, we use KILL_NEVER and
528 * take the lesser of two evils.
531 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
533 apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
536 /* APR pipe objects inherit by default. But we don't want the
537 * tunnel agent's pipes held open by future child processes
538 * (such as other ra_svn sessions), so turn that off. */
539 apr_file_inherit_unset(proc->in);
540 apr_file_inherit_unset(proc->out);
542 /* Guard against dotfile output to stdout on the server. */
543 *conn = svn_ra_svn_create_conn4(NULL,
544 svn_stream_from_aprfile2(proc->out, FALSE,
546 svn_stream_from_aprfile2(proc->in, FALSE,
548 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
550 err = svn_ra_svn__skip_leading_garbage(*conn, pool);
552 return svn_error_quick_wrap(
554 _("To better debug SSH connection problems, remove the -q "
555 "option from 'ssh' in the [tunnels] section of your "
556 "Subversion configuration file."));
561 /* Parse URL inot URI, validating it and setting the default port if none
562 was given. Allocate the URI fileds out of POOL. */
563 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
566 apr_status_t apr_err;
568 apr_err = apr_uri_parse(pool, url, uri);
571 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
572 _("Illegal svn repository URL '%s'"), url);
577 /* This structure is used as a baton for the pool cleanup function to
578 store tunnel parameters used by the close-tunnel callback. */
579 struct tunnel_data_t {
580 void *tunnel_context;
582 svn_ra_close_tunnel_func_t close_tunnel;
583 svn_stream_t *request;
584 svn_stream_t *response;
587 /* Pool cleanup function that invokes the close-tunnel callback. */
588 static apr_status_t close_tunnel_cleanup(void *baton)
590 const struct tunnel_data_t *const td = baton;
592 if (td->close_tunnel)
593 td->close_tunnel(td->tunnel_context, td->tunnel_baton);
595 svn_error_clear(svn_stream_close(td->request));
597 /* We might have one stream to use for both request and response! */
598 if (td->request != td->response)
599 svn_error_clear(svn_stream_close(td->response));
601 return APR_SUCCESS; /* ignored */
604 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
605 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
606 are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL,
607 it is the name of the tunnel type parsed from the URL scheme.
608 If TUNNEL_ARGV is not NULL, it points to a program argument list to use
609 when invoking the tunnel agent.
611 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
613 const apr_uri_t *uri,
614 const char *tunnel_name,
615 const char **tunnel_argv,
617 const svn_ra_callbacks2_t *callbacks,
618 void *callbacks_baton,
619 svn_auth_baton_t *auth_baton,
620 apr_pool_t *result_pool,
621 apr_pool_t *scratch_pool)
623 svn_ra_svn__session_baton_t *sess;
624 svn_ra_svn_conn_t *conn;
626 apr_uint64_t minver, maxver;
627 apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
628 const char *client_string = NULL;
629 apr_pool_t *pool = result_pool;
631 sess = apr_palloc(pool, sizeof(*sess));
633 sess->is_tunneled = (tunnel_name != NULL);
634 sess->url = apr_pstrdup(pool, url);
635 sess->user = uri->user;
636 sess->hostname = uri->hostname;
637 sess->tunnel_name = tunnel_name;
638 sess->tunnel_argv = tunnel_argv;
639 sess->callbacks = callbacks;
640 sess->callbacks_baton = callbacks_baton;
641 sess->bytes_read = sess->bytes_written = 0;
642 sess->auth_baton = auth_baton;
645 SVN_ERR(svn_config_copy_config(&sess->config, config, pool));
651 sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>",
653 uri->hostname, uri->port);
656 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
659 struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td));
661 td->tunnel_baton = callbacks->tunnel_baton;
662 td->close_tunnel = NULL;
664 SVN_ERR(callbacks->open_tunnel_func(
665 &td->request, &td->response,
666 &td->close_tunnel, &td->tunnel_context,
667 callbacks->tunnel_baton, tunnel_name,
668 uri->user, uri->hostname, uri->port,
669 callbacks->cancel_func, callbacks_baton,
672 apr_pool_cleanup_register(pool, td, close_tunnel_cleanup,
673 apr_pool_cleanup_null);
675 conn = svn_ra_svn_create_conn4(NULL, td->response, td->request,
676 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
678 SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool));
683 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
684 uri->port ? uri->port : SVN_RA_SVN_PORT);
686 SVN_ERR(make_connection(uri->hostname,
687 uri->port ? uri->port : SVN_RA_SVN_PORT,
689 conn = svn_ra_svn_create_conn4(sock, NULL, NULL,
690 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
694 /* Build the useragent string, querying the client for any
695 customizations it wishes to note. For historical reasons, we
696 still deliver the hard-coded client version info
697 (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
698 separately in the protocol/capabilities handshake below. But the
699 commit logic wants the combined form for use with the
700 SVN_PROP_TXN_USER_AGENT ephemeral property because that's
701 consistent with our DAV approach. */
702 if (sess->callbacks->get_client_string != NULL)
703 SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
704 &client_string, pool));
706 sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
707 client_string, SVN_VA_NULL);
709 sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
711 /* Make sure we set conn->session before reading from it,
712 * because the reader and writer functions expect a non-NULL value. */
714 conn->session = sess;
716 /* Read server's greeting. */
717 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
718 &mechlist, &server_caplist));
720 /* We support protocol version 2. */
722 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
723 _("Server requires minimum version %d"),
726 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
727 _("Server only supports versions up to %d"),
729 SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
731 /* All released versions of Subversion support edit-pipeline,
732 * so we do not support servers that do not. */
733 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
734 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
735 _("Server does not support edit pipelining"));
737 /* In protocol version 2, we send back our protocol version, our
738 * capability list, and the URL, and subsequently there is an auth
740 /* Client-side capabilities list: */
741 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
743 SVN_RA_SVN_CAP_EDIT_PIPELINE,
744 SVN_RA_SVN_CAP_SVNDIFF1,
745 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
746 SVN_RA_SVN_CAP_DEPTH,
747 SVN_RA_SVN_CAP_MERGEINFO,
748 SVN_RA_SVN_CAP_LOG_REVPROPS,
750 SVN_RA_SVN__DEFAULT_USERAGENT,
752 SVN_ERR(handle_auth_request(sess, pool));
754 /* This is where the security layer would go into effect if we
755 * supported security layers, which is a ways off. */
757 /* Read the repository's uuid and root URL, and perhaps learn more
758 capabilities that weren't available before now. */
759 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
760 &conn->repos_root, &repos_caplist));
762 SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
764 if (conn->repos_root)
766 conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
767 /* We should check that the returned string is a prefix of url, since
768 that's the API guarantee, but this isn't true for 1.0 servers.
769 Checking the length prevents client crashes. */
770 if (strlen(conn->repos_root) > strlen(url))
771 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
772 _("Impossibly long repository root from "
783 #define RA_SVN_DESCRIPTION \
784 N_("Module for accessing a repository using the svn network protocol.\n" \
785 " - with Cyrus SASL authentication")
787 #define RA_SVN_DESCRIPTION \
788 N_("Module for accessing a repository using the svn network protocol.")
791 static const char *ra_svn_get_description(apr_pool_t *pool)
793 return _(RA_SVN_DESCRIPTION);
796 static const char * const *
797 ra_svn_get_schemes(apr_pool_t *pool)
799 static const char *schemes[] = { "svn", NULL };
806 static svn_error_t *ra_svn_open(svn_ra_session_t *session,
807 const char **corrected_url,
809 const svn_ra_callbacks2_t *callbacks,
810 void *callback_baton,
811 svn_auth_baton_t *auth_baton,
813 apr_pool_t *result_pool,
814 apr_pool_t *scratch_pool)
816 apr_pool_t *sess_pool = svn_pool_create(result_pool);
817 svn_ra_svn__session_baton_t *sess;
818 const char *tunnel, **tunnel_argv;
820 svn_config_t *cfg, *cfg_client;
822 /* We don't support server-prescribed redirections in ra-svn. */
824 *corrected_url = NULL;
826 SVN_ERR(parse_url(url, &uri, sess_pool));
828 parse_tunnel(url, &tunnel, result_pool);
830 /* Use the default tunnel implementation if we got a tunnel name,
831 but either do not have tunnel handler callbacks installed, or
832 the handlers don't like the tunnel name. */
834 && (!callbacks->open_tunnel_func
835 || (callbacks->check_tunnel_func && callbacks->open_tunnel_func
836 && !callbacks->check_tunnel_func(callbacks->tunnel_baton,
838 SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
844 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
846 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
847 svn_auth_set_parameter(auth_baton,
848 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
849 svn_auth_set_parameter(auth_baton,
850 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
852 /* We open the session in a subpool so we can get rid of it if we
853 reparent with a server that doesn't support reparenting. */
854 SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config,
855 callbacks, callback_baton,
856 auth_baton, sess_pool, scratch_pool));
857 session->priv = sess;
862 static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session,
863 svn_ra_session_t *old_session,
864 const char *new_session_url,
865 apr_pool_t *result_pool,
866 apr_pool_t *scratch_pool)
868 svn_ra_svn__session_baton_t *old_sess = old_session->priv;
870 SVN_ERR(ra_svn_open(new_session, NULL, new_session_url,
871 old_sess->callbacks, old_sess->callbacks_baton,
872 old_sess->auth_baton, old_sess->config,
873 result_pool, scratch_pool));
878 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
882 svn_ra_svn__session_baton_t *sess = ra_session->priv;
883 svn_ra_svn_conn_t *conn = sess->conn;
885 apr_pool_t *sess_pool;
886 svn_ra_svn__session_baton_t *new_sess;
889 SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
890 err = handle_auth_request(sess, pool);
893 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
894 sess->url = apr_pstrdup(sess->pool, url);
897 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
900 /* Servers before 1.4 doesn't support this command; try to reconnect
902 svn_error_clear(err);
903 /* Create a new subpool of the RA session pool. */
904 sess_pool = svn_pool_create(ra_session->pool);
905 err = parse_url(url, &uri, sess_pool);
907 err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv,
908 sess->config, sess->callbacks, sess->callbacks_baton,
909 sess->auth_baton, sess_pool, sess_pool);
910 /* We destroy the new session pool on error, since it is allocated in
911 the main session pool. */
914 svn_pool_destroy(sess_pool);
918 /* We have a new connection, assign it and destroy the old. */
919 ra_session->priv = new_sess;
920 svn_pool_destroy(sess->pool);
925 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
926 const char **url, apr_pool_t *pool)
928 svn_ra_svn__session_baton_t *sess = session->priv;
929 *url = apr_pstrdup(pool, sess->url);
933 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
934 svn_revnum_t *rev, apr_pool_t *pool)
936 svn_ra_svn__session_baton_t *sess_baton = session->priv;
937 svn_ra_svn_conn_t *conn = sess_baton->conn;
939 SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
940 SVN_ERR(handle_auth_request(sess_baton, pool));
941 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
945 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
946 svn_revnum_t *rev, apr_time_t tm,
949 svn_ra_svn__session_baton_t *sess_baton = session->priv;
950 svn_ra_svn_conn_t *conn = sess_baton->conn;
952 SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
953 SVN_ERR(handle_auth_request(sess_baton, pool));
954 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
958 /* Forward declaration. */
959 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
961 const char *capability,
964 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
966 const svn_string_t *const *old_value_p,
967 const svn_string_t *value,
970 svn_ra_svn__session_baton_t *sess_baton = session->priv;
971 svn_ra_svn_conn_t *conn = sess_baton->conn;
972 svn_boolean_t dont_care;
973 const svn_string_t *old_value;
974 svn_boolean_t has_atomic_revprops;
976 SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
977 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
982 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
983 SVN_ERR_ASSERT(has_atomic_revprops);
986 old_value = *old_value_p;
994 if (has_atomic_revprops)
995 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
999 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
1002 SVN_ERR(handle_auth_request(sess_baton, pool));
1003 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1004 return SVN_NO_ERROR;
1007 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
1010 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1011 svn_ra_svn_conn_t *conn = sess_baton->conn;
1014 return SVN_NO_ERROR;
1017 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
1020 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1021 svn_ra_svn_conn_t *conn = sess_baton->conn;
1023 if (!conn->repos_root)
1024 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
1025 _("Server did not send repository root"));
1026 *url = conn->repos_root;
1027 return SVN_NO_ERROR;
1030 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
1031 apr_hash_t **props, apr_pool_t *pool)
1033 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1034 svn_ra_svn_conn_t *conn = sess_baton->conn;
1035 apr_array_header_t *proplist;
1037 SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
1038 SVN_ERR(handle_auth_request(sess_baton, pool));
1039 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
1040 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1041 return SVN_NO_ERROR;
1044 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1046 svn_string_t **value, apr_pool_t *pool)
1048 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1049 svn_ra_svn_conn_t *conn = sess_baton->conn;
1051 SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
1052 SVN_ERR(handle_auth_request(sess_baton, pool));
1053 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
1054 return SVN_NO_ERROR;
1057 static svn_error_t *ra_svn_end_commit(void *baton)
1059 ra_svn_commit_callback_baton_t *ccb = baton;
1060 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
1062 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
1063 SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
1065 &(commit_info->revision),
1066 &(commit_info->date),
1067 &(commit_info->author),
1068 &(commit_info->post_commit_err)));
1070 commit_info->repos_root = apr_pstrdup(ccb->pool,
1071 ccb->sess_baton->conn->repos_root);
1074 SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
1076 return SVN_NO_ERROR;
1079 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
1080 const svn_delta_editor_t **editor,
1082 apr_hash_t *revprop_table,
1083 svn_commit_callback2_t callback,
1084 void *callback_baton,
1085 apr_hash_t *lock_tokens,
1086 svn_boolean_t keep_locks,
1089 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1090 svn_ra_svn_conn_t *conn = sess_baton->conn;
1091 ra_svn_commit_callback_baton_t *ccb;
1092 apr_hash_index_t *hi;
1093 apr_pool_t *iterpool;
1094 const svn_string_t *log_msg = svn_hash_gets(revprop_table,
1095 SVN_PROP_REVISION_LOG);
1097 if (log_msg == NULL &&
1098 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1100 return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
1101 _("ra_svn does not support not specifying "
1102 "a log message with pre-1.5 servers; "
1103 "consider passing an empty one, or upgrading "
1106 else if (log_msg == NULL)
1107 /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument
1108 to the 'commit' protocol command is non-optional; on the server side,
1109 only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The
1110 "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
1111 will have a NULL log message (not just "", really NULL).
1113 svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
1114 present; this was elevated to a protocol promise in r1498550 (and
1115 later documented in this comment) in order to fix the segmentation
1116 fault bug described in the log message of r1498550.*/
1117 log_msg = svn_string_create("", pool);
1119 /* If we're sending revprops other than svn:log, make sure the server won't
1120 silently ignore them. */
1121 if (apr_hash_count(revprop_table) > 1 &&
1122 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1123 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1124 _("Server doesn't support setting arbitrary "
1125 "revision properties during commit"));
1127 /* If the server supports ephemeral txnprops, add the one that
1128 reports the client's version level string. */
1129 if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1130 svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1132 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1133 svn_string_create(SVN_VER_NUMBER, pool));
1134 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1135 svn_string_create(sess_baton->useragent, pool));
1138 /* Tell the server we're starting the commit.
1139 Send log message here for backwards compatibility with servers
1141 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1145 iterpool = svn_pool_create(pool);
1146 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1150 const char *path, *token;
1152 svn_pool_clear(iterpool);
1153 apr_hash_this(hi, &key, NULL, &val);
1156 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1158 svn_pool_destroy(iterpool);
1160 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1161 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1162 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1163 SVN_ERR(handle_auth_request(sess_baton, pool));
1164 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1166 /* Remember a few arguments for when the commit is over. */
1167 ccb = apr_palloc(pool, sizeof(*ccb));
1168 ccb->sess_baton = sess_baton;
1170 ccb->new_rev = NULL;
1171 ccb->callback = callback;
1172 ccb->callback_baton = callback_baton;
1174 /* Fetch an editor for the caller to drive. The editor will call
1175 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1176 * in the new_rev, committed_date, and committed_author values. */
1177 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1178 ra_svn_end_commit, ccb);
1179 return SVN_NO_ERROR;
1182 /* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1183 const char * repos relative paths and properties for those paths, storing
1184 the result as an array of svn_prop_inherited_item_t *items. */
1185 static svn_error_t *
1186 parse_iproplist(apr_array_header_t **inherited_props,
1187 const apr_array_header_t *iproplist,
1188 svn_ra_session_t *session,
1189 apr_pool_t *result_pool,
1190 apr_pool_t *scratch_pool)
1194 const char *repos_root_url;
1195 apr_pool_t *iterpool;
1197 if (iproplist == NULL)
1199 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1200 capability we shouldn't be asking for inherited props, but if we
1201 did and the server sent back nothing then we'll want to handle
1203 *inherited_props = NULL;
1204 return SVN_NO_ERROR;
1207 SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
1209 *inherited_props = apr_array_make(
1210 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1212 iterpool = svn_pool_create(scratch_pool);
1214 for (i = 0; i < iproplist->nelts; i++)
1216 apr_array_header_t *iprop_list;
1217 char *parent_rel_path;
1219 apr_hash_index_t *hi;
1220 svn_prop_inherited_item_t *new_iprop =
1221 apr_palloc(result_pool, sizeof(*new_iprop));
1222 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1224 if (elt->kind != SVN_RA_SVN_LIST)
1225 return svn_error_create(
1226 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1227 _("Inherited proplist element not a list"));
1229 svn_pool_clear(iterpool);
1231 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1232 &parent_rel_path, &iprop_list));
1233 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1234 new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
1237 new_iprop->prop_hash = svn_hash__make(result_pool);
1238 for (hi = apr_hash_first(iterpool, iprops);
1240 hi = apr_hash_next(hi))
1242 const char *name = apr_hash_this_key(hi);
1243 svn_string_t *value = apr_hash_this_val(hi);
1244 svn_hash_sets(new_iprop->prop_hash,
1245 apr_pstrdup(result_pool, name),
1246 svn_string_dup(value, result_pool));
1248 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1251 svn_pool_destroy(iterpool);
1252 return SVN_NO_ERROR;
1255 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1256 svn_revnum_t rev, svn_stream_t *stream,
1257 svn_revnum_t *fetched_rev,
1261 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1262 svn_ra_svn_conn_t *conn = sess_baton->conn;
1263 apr_array_header_t *proplist;
1264 const char *expected_digest;
1265 svn_checksum_t *expected_checksum = NULL;
1266 svn_checksum_ctx_t *checksum_ctx;
1267 apr_pool_t *iterpool;
1269 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1270 (props != NULL), (stream != NULL)));
1271 SVN_ERR(handle_auth_request(sess_baton, pool));
1272 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1279 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1281 /* We're done if the contents weren't wanted. */
1283 return SVN_NO_ERROR;
1285 if (expected_digest)
1287 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1288 expected_digest, pool));
1289 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1292 /* Read the file's contents. */
1293 iterpool = svn_pool_create(pool);
1296 svn_ra_svn_item_t *item;
1298 svn_pool_clear(iterpool);
1299 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1300 if (item->kind != SVN_RA_SVN_STRING)
1301 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1302 _("Non-string as part of file contents"));
1303 if (item->u.string->len == 0)
1306 if (expected_checksum)
1307 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1308 item->u.string->len));
1310 SVN_ERR(svn_stream_write(stream, item->u.string->data,
1311 &item->u.string->len));
1313 svn_pool_destroy(iterpool);
1315 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1317 if (expected_checksum)
1319 svn_checksum_t *checksum;
1321 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1322 if (!svn_checksum_match(checksum, expected_checksum))
1323 return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1324 _("Checksum mismatch for '%s'"),
1328 return SVN_NO_ERROR;
1331 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1332 apr_hash_t **dirents,
1333 svn_revnum_t *fetched_rev,
1337 apr_uint32_t dirent_fields,
1340 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1341 svn_ra_svn_conn_t *conn = sess_baton->conn;
1342 apr_array_header_t *proplist, *dirlist;
1345 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1346 rev, (props != NULL), (dirents != NULL)));
1347 if (dirent_fields & SVN_DIRENT_KIND)
1348 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1349 if (dirent_fields & SVN_DIRENT_SIZE)
1350 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1351 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1352 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1353 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1354 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1355 if (dirent_fields & SVN_DIRENT_TIME)
1356 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1357 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1358 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1360 /* Always send the, nominally optional, want-iprops as "false" to
1361 workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1362 to see "true" if it is omitted. */
1363 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1365 SVN_ERR(handle_auth_request(sess_baton, pool));
1366 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1372 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1374 /* We're done if dirents aren't wanted. */
1376 return SVN_NO_ERROR;
1378 /* Interpret the directory list. */
1379 *dirents = svn_hash__make(pool);
1380 for (i = 0; i < dirlist->nelts; i++)
1382 const char *name, *kind, *cdate, *cauthor;
1383 svn_boolean_t has_props;
1384 svn_dirent_t *dirent;
1387 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1389 if (elt->kind != SVN_RA_SVN_LIST)
1390 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1391 _("Dirlist element not a list"));
1392 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1393 &name, &kind, &size, &has_props,
1394 &crev, &cdate, &cauthor));
1396 /* Nothing to sanitize here. Any multi-segment path is simply
1397 illegal in the hash returned by svn_ra_get_dir2. */
1398 if (strchr(name, '/'))
1399 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1400 _("Invalid directory entry name '%s'"),
1403 dirent = svn_dirent_create(pool);
1404 dirent->kind = svn_node_kind_from_word(kind);
1405 dirent->size = size;/* FIXME: svn_filesize_t */
1406 dirent->has_props = has_props;
1407 dirent->created_rev = crev;
1408 /* NOTE: the tuple's format string says CDATE may be NULL. But this
1409 function does not allow that. The server has always sent us some
1410 random date, however, so this just happens to work. But let's
1411 be wary of servers that are (improperly) fixed to send NULL.
1413 Note: they should NOT be "fixed" to send NULL, as that would break
1414 any older clients which received that NULL. But we may as well
1415 be defensive against a malicous server. */
1419 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1420 dirent->last_author = cauthor;
1421 svn_hash_sets(*dirents, name, dirent);
1424 return SVN_NO_ERROR;
1427 /* Converts a apr_uint64_t with values TRUE, FALSE or
1428 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1429 to a svn_tristate_t */
1430 static svn_tristate_t
1431 optbool_to_tristate(apr_uint64_t v)
1433 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
1434 return svn_tristate_true;
1436 return svn_tristate_false;
1438 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1441 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1442 server, which defaults to youngest. */
1443 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1444 svn_mergeinfo_catalog_t *catalog,
1445 const apr_array_header_t *paths,
1446 svn_revnum_t revision,
1447 svn_mergeinfo_inheritance_t inherit,
1448 svn_boolean_t include_descendants,
1451 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1452 svn_ra_svn_conn_t *conn = sess_baton->conn;
1454 apr_array_header_t *mergeinfo_tuple;
1455 svn_ra_svn_item_t *elt;
1458 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1459 for (i = 0; i < paths->nelts; i++)
1461 path = APR_ARRAY_IDX(paths, i, const char *);
1462 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1464 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1465 svn_inheritance_to_word(inherit),
1466 include_descendants));
1468 SVN_ERR(handle_auth_request(sess_baton, pool));
1469 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1472 if (mergeinfo_tuple->nelts > 0)
1474 *catalog = svn_hash__make(pool);
1475 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1477 svn_mergeinfo_t for_path;
1478 const char *to_parse;
1480 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1481 if (elt->kind != SVN_RA_SVN_LIST)
1482 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1483 _("Mergeinfo element is not a list"));
1484 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1486 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1487 /* Correct for naughty servers that send "relative" paths
1488 with leading slashes! */
1489 svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1493 return SVN_NO_ERROR;
1496 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1497 const svn_ra_reporter3_t **reporter,
1498 void **report_baton, svn_revnum_t rev,
1499 const char *target, svn_depth_t depth,
1500 svn_boolean_t send_copyfrom_args,
1501 svn_boolean_t ignore_ancestry,
1502 const svn_delta_editor_t *update_editor,
1505 apr_pool_t *scratch_pool)
1507 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1508 svn_ra_svn_conn_t *conn = sess_baton->conn;
1509 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1511 /* Tell the server we want to start an update. */
1512 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1513 depth, send_copyfrom_args,
1515 SVN_ERR(handle_auth_request(sess_baton, pool));
1517 /* Fetch a reporter for the caller to drive. The reporter will drive
1518 * update_editor upon finish_report(). */
1519 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1520 target, depth, reporter, report_baton));
1521 return SVN_NO_ERROR;
1524 static svn_error_t *
1525 ra_svn_switch(svn_ra_session_t *session,
1526 const svn_ra_reporter3_t **reporter,
1527 void **report_baton, svn_revnum_t rev,
1528 const char *target, svn_depth_t depth,
1529 const char *switch_url,
1530 svn_boolean_t send_copyfrom_args,
1531 svn_boolean_t ignore_ancestry,
1532 const svn_delta_editor_t *update_editor,
1534 apr_pool_t *result_pool,
1535 apr_pool_t *scratch_pool)
1537 apr_pool_t *pool = result_pool;
1538 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1539 svn_ra_svn_conn_t *conn = sess_baton->conn;
1540 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1542 /* Tell the server we want to start a switch. */
1543 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1545 send_copyfrom_args, ignore_ancestry));
1546 SVN_ERR(handle_auth_request(sess_baton, pool));
1548 /* Fetch a reporter for the caller to drive. The reporter will drive
1549 * update_editor upon finish_report(). */
1550 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1551 target, depth, reporter, report_baton));
1552 return SVN_NO_ERROR;
1555 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1556 const svn_ra_reporter3_t **reporter,
1557 void **report_baton,
1558 const char *target, svn_revnum_t rev,
1560 const svn_delta_editor_t *status_editor,
1561 void *status_baton, apr_pool_t *pool)
1563 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1564 svn_ra_svn_conn_t *conn = sess_baton->conn;
1565 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1567 /* Tell the server we want to start a status operation. */
1568 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1570 SVN_ERR(handle_auth_request(sess_baton, pool));
1572 /* Fetch a reporter for the caller to drive. The reporter will drive
1573 * status_editor upon finish_report(). */
1574 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1575 target, depth, reporter, report_baton));
1576 return SVN_NO_ERROR;
1579 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1580 const svn_ra_reporter3_t **reporter,
1581 void **report_baton,
1582 svn_revnum_t rev, const char *target,
1584 svn_boolean_t ignore_ancestry,
1585 svn_boolean_t text_deltas,
1586 const char *versus_url,
1587 const svn_delta_editor_t *diff_editor,
1588 void *diff_baton, apr_pool_t *pool)
1590 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1591 svn_ra_svn_conn_t *conn = sess_baton->conn;
1592 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1594 /* Tell the server we want to start a diff. */
1595 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1596 ignore_ancestry, versus_url,
1597 text_deltas, depth));
1598 SVN_ERR(handle_auth_request(sess_baton, pool));
1600 /* Fetch a reporter for the caller to drive. The reporter will drive
1601 * diff_editor upon finish_report(). */
1602 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1603 target, depth, reporter, report_baton));
1604 return SVN_NO_ERROR;
1608 static svn_error_t *
1609 perform_ra_svn_log(svn_error_t **outer_error,
1610 svn_ra_session_t *session,
1611 const apr_array_header_t *paths,
1612 svn_revnum_t start, svn_revnum_t end,
1614 svn_boolean_t discover_changed_paths,
1615 svn_boolean_t strict_node_history,
1616 svn_boolean_t include_merged_revisions,
1617 const apr_array_header_t *revprops,
1618 svn_log_entry_receiver_t receiver,
1619 void *receiver_baton,
1622 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1623 svn_ra_svn_conn_t *conn = sess_baton->conn;
1624 apr_pool_t *iterpool;
1629 svn_boolean_t want_custom_revprops;
1630 svn_boolean_t want_author = FALSE;
1631 svn_boolean_t want_message = FALSE;
1632 svn_boolean_t want_date = FALSE;
1635 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1638 for (i = 0; i < paths->nelts; i++)
1640 path = APR_ARRAY_IDX(paths, i, const char *);
1641 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1644 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1645 discover_changed_paths, strict_node_history,
1646 (apr_uint64_t) limit,
1647 include_merged_revisions));
1650 want_custom_revprops = FALSE;
1651 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1652 for (i = 0; i < revprops->nelts; i++)
1654 name = APR_ARRAY_IDX(revprops, i, char *);
1655 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1657 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1659 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1661 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1662 want_message = TRUE;
1664 want_custom_revprops = TRUE;
1666 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1670 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1674 want_message = TRUE;
1675 want_custom_revprops = TRUE;
1678 SVN_ERR(handle_auth_request(sess_baton, pool));
1680 /* Read the log messages. */
1681 iterpool = svn_pool_create(pool);
1684 apr_uint64_t has_children_param, invalid_revnum_param;
1685 apr_uint64_t has_subtractive_merge_param;
1686 svn_string_t *author, *date, *message;
1687 apr_array_header_t *cplist, *rplist;
1688 svn_log_entry_t *log_entry;
1689 svn_boolean_t has_children;
1690 svn_boolean_t subtractive_merge = FALSE;
1691 apr_uint64_t revprop_count;
1692 svn_ra_svn_item_t *item;
1696 svn_pool_clear(iterpool);
1697 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1698 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1700 if (item->kind != SVN_RA_SVN_LIST)
1701 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1702 _("Log entry not a list"));
1703 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1704 "lr(?s)(?s)(?s)?BBnl?B",
1705 &cplist, &rev, &author, &date,
1706 &message, &has_children_param,
1707 &invalid_revnum_param,
1708 &revprop_count, &rplist,
1709 &has_subtractive_merge_param));
1710 if (want_custom_revprops && rplist == NULL)
1712 /* Caller asked for custom revprops, but server is too old. */
1713 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1714 _("Server does not support custom revprops"
1718 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1719 has_children = FALSE;
1721 has_children = (svn_boolean_t) has_children_param;
1723 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1724 subtractive_merge = FALSE;
1726 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1728 /* Because the svn protocol won't let us send an invalid revnum, we have
1729 to recover that fact using the extra parameter. */
1730 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1731 && invalid_revnum_param)
1732 rev = SVN_INVALID_REVNUM;
1734 if (cplist->nelts > 0)
1736 /* Interpret the changed-paths list. */
1737 cphash = svn_hash__make(iterpool);
1738 for (i = 0; i < cplist->nelts; i++)
1740 svn_log_changed_path2_t *change;
1741 svn_string_t *cpath;
1742 const char *copy_path, *action, *kind_str;
1743 apr_uint64_t text_mods, prop_mods;
1744 svn_revnum_t copy_rev;
1745 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1748 if (elt->kind != SVN_RA_SVN_LIST)
1749 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1750 _("Changed-path entry not a list"));
1751 SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1752 &cpath, &action, ©_path,
1753 ©_rev, &kind_str,
1754 &text_mods, &prop_mods));
1756 if (!svn_fspath__is_canonical(cpath->data))
1758 cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1759 cpath->len = strlen(cpath->data);
1761 if (copy_path && !svn_fspath__is_canonical(copy_path))
1762 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1764 change = svn_log_changed_path2_create(iterpool);
1765 change->action = *action;
1766 change->copyfrom_path = copy_path;
1767 change->copyfrom_rev = copy_rev;
1768 change->node_kind = svn_node_kind_from_word(kind_str);
1769 change->text_modified = optbool_to_tristate(text_mods);
1770 change->props_modified = optbool_to_tristate(prop_mods);
1771 apr_hash_set(cphash, cpath->data, cpath->len, change);
1778 - Except if the server sends more than a >= 1 limit top level items
1779 - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1780 in an earlier invocation. */
1781 if (! (limit && (nest_level == 0) && (++nreceived > limit))
1785 log_entry = svn_log_entry_create(iterpool);
1787 log_entry->changed_paths = cphash;
1788 log_entry->changed_paths2 = cphash;
1789 log_entry->revision = rev;
1790 log_entry->has_children = has_children;
1791 log_entry->subtractive_merge = subtractive_merge;
1793 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1794 &log_entry->revprops));
1795 if (log_entry->revprops == NULL)
1796 log_entry->revprops = svn_hash__make(iterpool);
1798 if (author && want_author)
1799 svn_hash_sets(log_entry->revprops,
1800 SVN_PROP_REVISION_AUTHOR, author);
1801 if (date && want_date)
1802 svn_hash_sets(log_entry->revprops,
1803 SVN_PROP_REVISION_DATE, date);
1804 if (message && want_message)
1805 svn_hash_sets(log_entry->revprops,
1806 SVN_PROP_REVISION_LOG, message);
1808 err = receiver(receiver_baton, log_entry, iterpool);
1809 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1811 *outer_error = svn_error_trace(
1812 svn_error_compose_create(*outer_error, err));
1817 if (log_entry->has_children)
1821 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1823 SVN_ERR_ASSERT(nest_level);
1828 svn_pool_destroy(iterpool);
1830 /* Read the response. */
1831 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1834 static svn_error_t *
1835 ra_svn_log(svn_ra_session_t *session,
1836 const apr_array_header_t *paths,
1837 svn_revnum_t start, svn_revnum_t end,
1839 svn_boolean_t discover_changed_paths,
1840 svn_boolean_t strict_node_history,
1841 svn_boolean_t include_merged_revisions,
1842 const apr_array_header_t *revprops,
1843 svn_log_entry_receiver_t receiver,
1844 void *receiver_baton, apr_pool_t *pool)
1846 svn_error_t *outer_error = NULL;
1849 err = svn_error_trace(perform_ra_svn_log(&outer_error,
1853 discover_changed_paths,
1854 strict_node_history,
1855 include_merged_revisions,
1857 receiver, receiver_baton,
1859 return svn_error_trace(
1860 svn_error_compose_create(outer_error,
1866 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1867 const char *path, svn_revnum_t rev,
1868 svn_node_kind_t *kind, apr_pool_t *pool)
1870 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1871 svn_ra_svn_conn_t *conn = sess_baton->conn;
1872 const char *kind_word;
1874 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1875 SVN_ERR(handle_auth_request(sess_baton, pool));
1876 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1877 *kind = svn_node_kind_from_word(kind_word);
1878 return SVN_NO_ERROR;
1882 /* If ERR is a command not supported error, wrap it in a
1883 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1884 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1887 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1888 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1894 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1895 const char *path, svn_revnum_t rev,
1896 svn_dirent_t **dirent, apr_pool_t *pool)
1898 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1899 svn_ra_svn_conn_t *conn = sess_baton->conn;
1900 apr_array_header_t *list = NULL;
1901 svn_dirent_t *the_dirent;
1903 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1904 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1905 N_("'stat' not implemented")));
1906 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1914 const char *kind, *cdate, *cauthor;
1915 svn_boolean_t has_props;
1919 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1920 &kind, &size, &has_props,
1921 &crev, &cdate, &cauthor));
1923 the_dirent = svn_dirent_create(pool);
1924 the_dirent->kind = svn_node_kind_from_word(kind);
1925 the_dirent->size = size;/* FIXME: svn_filesize_t */
1926 the_dirent->has_props = has_props;
1927 the_dirent->created_rev = crev;
1928 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1929 the_dirent->last_author = cauthor;
1931 *dirent = the_dirent;
1934 return SVN_NO_ERROR;
1938 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1939 apr_hash_t **locations,
1941 svn_revnum_t peg_revision,
1942 const apr_array_header_t *location_revisions,
1945 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1946 svn_ra_svn_conn_t *conn = sess_baton->conn;
1947 svn_revnum_t revision;
1948 svn_boolean_t is_done;
1951 /* Transmit the parameters. */
1952 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1953 "get-locations", path, peg_revision));
1954 for (i = 0; i < location_revisions->nelts; i++)
1956 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1957 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1960 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1962 /* Servers before 1.1 don't support this command. Check for this here. */
1963 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1964 N_("'get-locations' not implemented")));
1966 /* Read the hash items. */
1968 *locations = apr_hash_make(pool);
1971 svn_ra_svn_item_t *item;
1972 const char *ret_path;
1974 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1975 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1977 else if (item->kind != SVN_RA_SVN_LIST)
1978 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1979 _("Location entry not a list"));
1982 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1983 &revision, &ret_path));
1984 ret_path = svn_fspath__canonicalize(ret_path, pool);
1985 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1987 sizeof(revision), ret_path);
1991 /* Read the response. This is so the server would have a chance to
1992 * report an error. */
1993 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1996 static svn_error_t *
1997 ra_svn_get_location_segments(svn_ra_session_t *session,
1999 svn_revnum_t peg_revision,
2000 svn_revnum_t start_rev,
2001 svn_revnum_t end_rev,
2002 svn_location_segment_receiver_t receiver,
2003 void *receiver_baton,
2006 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2007 svn_ra_svn_conn_t *conn = sess_baton->conn;
2008 svn_boolean_t is_done;
2009 apr_pool_t *iterpool = svn_pool_create(pool);
2011 /* Transmit the parameters. */
2012 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2013 "get-location-segments",
2014 path, peg_revision, start_rev, end_rev));
2016 /* Servers before 1.5 don't support this command. Check for this here. */
2017 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2018 N_("'get-location-segments'"
2019 " not implemented")));
2021 /* Parse the response. */
2025 svn_revnum_t range_start, range_end;
2026 svn_ra_svn_item_t *item;
2027 const char *ret_path;
2029 svn_pool_clear(iterpool);
2030 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2031 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2033 else if (item->kind != SVN_RA_SVN_LIST)
2034 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2035 _("Location segment entry not a list"));
2038 svn_location_segment_t *segment = apr_pcalloc(iterpool,
2040 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2041 &range_start, &range_end, &ret_path));
2042 if (! (SVN_IS_VALID_REVNUM(range_start)
2043 && SVN_IS_VALID_REVNUM(range_end)))
2044 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2045 _("Expected valid revision range"));
2047 ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2048 segment->path = ret_path;
2049 segment->range_start = range_start;
2050 segment->range_end = range_end;
2051 SVN_ERR(receiver(segment, receiver_baton, iterpool));
2054 svn_pool_destroy(iterpool);
2056 /* Read the response. This is so the server would have a chance to
2057 * report an error. */
2058 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2060 return SVN_NO_ERROR;
2063 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2065 svn_revnum_t start, svn_revnum_t end,
2066 svn_boolean_t include_merged_revisions,
2067 svn_file_rev_handler_t handler,
2068 void *handler_baton, apr_pool_t *pool)
2070 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2071 apr_pool_t *rev_pool, *chunk_pool;
2072 svn_boolean_t has_txdelta;
2073 svn_boolean_t had_revision = FALSE;
2075 /* One sub-pool for each revision and one for each txdelta chunk.
2076 Note that the rev_pool must live during the following txdelta. */
2077 rev_pool = svn_pool_create(pool);
2078 chunk_pool = svn_pool_create(pool);
2080 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2082 include_merged_revisions));
2084 /* Servers before 1.1 don't support this command. Check for this here. */
2085 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2086 N_("'get-file-revs' not implemented")));
2090 apr_array_header_t *rev_proplist, *proplist;
2091 apr_uint64_t merged_rev_param;
2092 apr_array_header_t *props;
2093 svn_ra_svn_item_t *item;
2094 apr_hash_t *rev_props;
2097 svn_boolean_t merged_rev;
2098 svn_txdelta_window_handler_t d_handler;
2101 svn_pool_clear(rev_pool);
2102 svn_pool_clear(chunk_pool);
2103 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2104 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2106 /* Either we've got a correct revision or we will error out below. */
2107 had_revision = TRUE;
2108 if (item->kind != SVN_RA_SVN_LIST)
2109 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2110 _("Revision entry not a list"));
2112 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2113 "crll?B", &p, &rev, &rev_proplist,
2114 &proplist, &merged_rev_param));
2115 p = svn_fspath__canonicalize(p, rev_pool);
2116 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2117 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2118 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2121 merged_rev = (svn_boolean_t) merged_rev_param;
2123 /* Get the first delta chunk so we know if there is a delta. */
2124 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2125 if (item->kind != SVN_RA_SVN_STRING)
2126 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2127 _("Text delta chunk not a string"));
2128 has_txdelta = item->u.string->len > 0;
2130 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2131 has_txdelta ? &d_handler : NULL, &d_baton,
2134 /* Process the text delta if any. */
2137 svn_stream_t *stream;
2139 if (d_handler && d_handler != svn_delta_noop_window_handler)
2140 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2144 while (item->u.string->len > 0)
2148 size = item->u.string->len;
2150 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2151 svn_pool_clear(chunk_pool);
2153 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2155 if (item->kind != SVN_RA_SVN_STRING)
2156 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2157 _("Text delta chunk not a string"));
2160 SVN_ERR(svn_stream_close(stream));
2164 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2166 /* Return error if we didn't get any revisions. */
2168 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2169 _("The get-file-revs command didn't return "
2172 svn_pool_destroy(chunk_pool);
2173 svn_pool_destroy(rev_pool);
2175 return SVN_NO_ERROR;
2178 /* For each path in PATH_REVS, send a 'lock' command to the server.
2179 Used with 1.2.x series servers which support locking, but of only
2180 one path at a time. ra_svn_lock(), which supports 'lock-many'
2181 is now the default. See svn_ra_lock() docstring for interface details. */
2182 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2183 apr_hash_t *path_revs,
2184 const char *comment,
2185 svn_boolean_t steal_lock,
2186 svn_ra_lock_callback_t lock_func,
2190 svn_ra_svn__session_baton_t *sess = session->priv;
2191 svn_ra_svn_conn_t* conn = sess->conn;
2192 apr_array_header_t *list;
2193 apr_hash_index_t *hi;
2194 apr_pool_t *iterpool = svn_pool_create(pool);
2196 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2202 svn_revnum_t *revnum;
2203 svn_error_t *err, *callback_err = NULL;
2205 svn_pool_clear(iterpool);
2207 apr_hash_this(hi, &key, NULL, &val);
2211 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2212 steal_lock, *revnum));
2214 /* Servers before 1.2 doesn't support locking. Check this here. */
2215 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2216 N_("Server doesn't support "
2217 "the lock command")));
2219 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2222 SVN_ERR(parse_lock(list, iterpool, &lock));
2224 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2228 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2231 svn_error_clear(err);
2234 return callback_err;
2237 svn_pool_destroy(iterpool);
2239 return SVN_NO_ERROR;
2242 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2243 Used with 1.2.x series servers which support unlocking, but of only
2244 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
2245 now the default. See svn_ra_unlock() docstring for interface details. */
2246 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2247 apr_hash_t *path_tokens,
2248 svn_boolean_t break_lock,
2249 svn_ra_lock_callback_t lock_func,
2253 svn_ra_svn__session_baton_t *sess = session->priv;
2254 svn_ra_svn_conn_t* conn = sess->conn;
2255 apr_hash_index_t *hi;
2256 apr_pool_t *iterpool = svn_pool_create(pool);
2258 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2264 svn_error_t *err, *callback_err = NULL;
2266 svn_pool_clear(iterpool);
2268 apr_hash_this(hi, &key, NULL, &val);
2270 if (strcmp(val, "") != 0)
2275 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2278 /* Servers before 1.2 don't support locking. Check this here. */
2279 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2280 N_("Server doesn't support the unlock "
2283 err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2285 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2289 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2291 svn_error_clear(err);
2294 return callback_err;
2297 svn_pool_destroy(iterpool);
2299 return SVN_NO_ERROR;
2302 /* Tell the server to lock all paths in PATH_REVS.
2303 See svn_ra_lock() for interface details. */
2304 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2305 apr_hash_t *path_revs,
2306 const char *comment,
2307 svn_boolean_t steal_lock,
2308 svn_ra_lock_callback_t lock_func,
2312 svn_ra_svn__session_baton_t *sess = session->priv;
2313 svn_ra_svn_conn_t *conn = sess->conn;
2314 apr_hash_index_t *hi;
2316 apr_pool_t *iterpool = svn_pool_create(pool);
2318 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2319 comment, steal_lock));
2321 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2326 svn_revnum_t *revnum;
2328 svn_pool_clear(iterpool);
2329 apr_hash_this(hi, &key, NULL, &val);
2333 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
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 'lock-many'. If that fails, fall back
2342 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2344 svn_error_clear(err);
2345 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2346 lock_func, lock_baton, pool);
2352 /* Loop over responses to get lock information. */
2353 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2355 svn_ra_svn_item_t *elt;
2358 svn_error_t *callback_err;
2361 apr_array_header_t *list;
2363 apr_hash_this(hi, &key, NULL, NULL);
2366 svn_pool_clear(iterpool);
2367 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2369 /* The server might have encountered some sort of fatal error in
2370 the middle of the request list. If this happens, it will
2371 transmit "done" to end the lock-info early, and then the
2372 overall command response will talk about the fatal error. */
2373 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2376 if (elt->kind != SVN_RA_SVN_LIST)
2377 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2378 _("Lock 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(parse_lock(list, iterpool, &lock));
2391 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2392 _("Unknown status for lock command"));
2395 callback_err = lock_func(lock_baton, path, TRUE,
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 lock "
2420 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2422 svn_pool_destroy(iterpool);
2424 return SVN_NO_ERROR;
2427 /* Tell the server to unlock all paths in PATH_TOKENS.
2428 See svn_ra_unlock() for interface details. */
2429 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2430 apr_hash_t *path_tokens,
2431 svn_boolean_t break_lock,
2432 svn_ra_lock_callback_t lock_func,
2436 svn_ra_svn__session_baton_t *sess = session->priv;
2437 svn_ra_svn_conn_t *conn = sess->conn;
2438 apr_hash_index_t *hi;
2439 apr_pool_t *iterpool = svn_pool_create(pool);
2443 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2446 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2452 svn_pool_clear(iterpool);
2453 apr_hash_this(hi, &key, NULL, &val);
2456 if (strcmp(val, "") != 0)
2461 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2464 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2466 err = handle_auth_request(sess, pool);
2468 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2471 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2473 svn_error_clear(err);
2474 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2481 /* Loop over responses to unlock files. */
2482 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2484 svn_ra_svn_item_t *elt;
2486 svn_error_t *callback_err;
2488 apr_array_header_t *list;
2490 svn_pool_clear(iterpool);
2492 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2494 /* The server might have encountered some sort of fatal error in
2495 the middle of the request list. If this happens, it will
2496 transmit "done" to end the lock-info early, and then the
2497 overall command response will talk about the fatal error. */
2498 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2501 apr_hash_this(hi, &key, NULL, NULL);
2504 if (elt->kind != SVN_RA_SVN_LIST)
2505 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2506 _("Unlock response not a list"));
2508 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2511 if (strcmp(status, "failure") == 0)
2512 err = svn_ra_svn__handle_failure_status(list, iterpool);
2513 else if (strcmp(status, "success") == 0)
2515 SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2519 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2520 _("Unknown status for unlock command"));
2523 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2526 callback_err = SVN_NO_ERROR;
2528 svn_error_clear(err);
2531 return callback_err;
2534 /* If we didn't break early above, and the whole hash was traversed,
2535 read the final "done" from the server. */
2538 svn_ra_svn_item_t *elt;
2540 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2541 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2542 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2543 _("Didn't receive end marker for unlock "
2547 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2549 svn_pool_destroy(iterpool);
2551 return SVN_NO_ERROR;
2554 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2559 svn_ra_svn__session_baton_t *sess = session->priv;
2560 svn_ra_svn_conn_t* conn = sess->conn;
2561 apr_array_header_t *list;
2563 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2565 /* Servers before 1.2 doesn't support locking. Check this here. */
2566 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2567 N_("Server doesn't support the get-lock "
2570 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2572 SVN_ERR(parse_lock(list, pool, lock));
2576 return SVN_NO_ERROR;
2579 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2580 to prevent a dependency cycle. */
2581 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2582 const char **rel_path,
2586 const char *root_url;
2588 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2589 *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2591 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2592 _("'%s' isn't a child of repository root "
2595 return SVN_NO_ERROR;
2598 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2604 svn_ra_svn__session_baton_t *sess = session->priv;
2605 svn_ra_svn_conn_t* conn = sess->conn;
2606 apr_array_header_t *list;
2607 const char *full_url, *abs_path;
2610 /* Figure out the repository abspath from PATH. */
2611 full_url = svn_path_url_add_component2(sess->url, path, pool);
2612 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2613 abs_path = svn_fspath__canonicalize(abs_path, pool);
2615 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2617 /* Servers before 1.2 doesn't support locking. Check this here. */
2618 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2619 N_("Server doesn't support the get-lock "
2622 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2624 *locks = apr_hash_make(pool);
2626 for (i = 0; i < list->nelts; ++i)
2629 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2631 if (elt->kind != SVN_RA_SVN_LIST)
2632 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2633 _("Lock element not a list"));
2634 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2636 /* Filter out unwanted paths. Since Subversion only allows
2637 locks on files, we can treat depth=immediates the same as
2638 depth=files for filtering purposes. Meaning, we'll keep
2641 a) its path is the very path we queried, or
2642 b) we've asked for a fully recursive answer, or
2643 c) we've asked for depth=files or depth=immediates, and this
2644 lock is on an immediate child of our query path.
2646 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2648 svn_hash_sets(*locks, lock->path, lock);
2650 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2652 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2653 if (relpath && (svn_path_component_count(relpath) == 1))
2654 svn_hash_sets(*locks, lock->path, lock);
2658 return SVN_NO_ERROR;
2662 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2663 svn_revnum_t revision,
2664 svn_revnum_t low_water_mark,
2665 svn_boolean_t send_deltas,
2666 const svn_delta_editor_t *editor,
2670 svn_ra_svn__session_baton_t *sess = session->priv;
2672 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2673 low_water_mark, send_deltas));
2675 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2676 N_("Server doesn't support the replay "
2679 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2682 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2686 static svn_error_t *
2687 ra_svn_replay_range(svn_ra_session_t *session,
2688 svn_revnum_t start_revision,
2689 svn_revnum_t end_revision,
2690 svn_revnum_t low_water_mark,
2691 svn_boolean_t send_deltas,
2692 svn_ra_replay_revstart_callback_t revstart_func,
2693 svn_ra_replay_revfinish_callback_t revfinish_func,
2697 svn_ra_svn__session_baton_t *sess = session->priv;
2698 apr_pool_t *iterpool;
2700 svn_boolean_t drive_aborted = FALSE;
2702 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2703 start_revision, end_revision,
2704 low_water_mark, send_deltas));
2706 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2707 N_("Server doesn't support the "
2708 "replay-range command")));
2710 iterpool = svn_pool_create(pool);
2711 for (rev = start_revision; rev <= end_revision; rev++)
2713 const svn_delta_editor_t *editor;
2715 apr_hash_t *rev_props;
2717 apr_array_header_t *list;
2719 svn_pool_clear(iterpool);
2721 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2722 "wl", &word, &list));
2723 if (strcmp(word, "revprops") != 0)
2724 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2725 _("Expected 'revprops', found '%s'"),
2728 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2730 SVN_ERR(revstart_func(rev, replay_baton,
2731 &editor, &edit_baton,
2734 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2736 &drive_aborted, TRUE));
2737 /* If drive_editor2() aborted the commit, do NOT try to call
2738 revfinish_func and commit the transaction! */
2739 if (drive_aborted) {
2740 svn_pool_destroy(iterpool);
2741 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2742 _("Error while replaying commit"));
2744 SVN_ERR(revfinish_func(rev, replay_baton,
2749 svn_pool_destroy(iterpool);
2751 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2755 static svn_error_t *
2756 ra_svn_has_capability(svn_ra_session_t *session,
2758 const char *capability,
2761 svn_ra_svn__session_baton_t *sess = session->priv;
2762 static const char* capabilities[][2] =
2764 /* { ra capability string, svn:// wire capability string} */
2765 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2766 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2767 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2768 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2769 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2770 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2771 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2772 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2773 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2774 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2775 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2777 {NULL, NULL} /* End of list marker */
2783 for (i = 0; capabilities[i][0]; i++)
2785 if (strcmp(capability, capabilities[i][0]) == 0)
2787 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2788 return SVN_NO_ERROR;
2792 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2793 _("Don't know anything about capability '%s'"),
2797 static svn_error_t *
2798 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2800 svn_revnum_t peg_revision,
2801 svn_revnum_t end_revision,
2802 svn_revnum_t *revision_deleted,
2806 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2807 svn_ra_svn_conn_t *conn = sess_baton->conn;
2809 /* Transmit the parameters. */
2810 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2811 peg_revision, end_revision));
2813 /* Servers before 1.6 don't support this command. Check for this here. */
2814 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2815 N_("'get-deleted-rev' not implemented")));
2817 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2821 static svn_error_t *
2822 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2823 svn_delta_shim_callbacks_t *callbacks)
2825 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2826 svn_ra_svn_conn_t *conn = sess_baton->conn;
2828 conn->shim_callbacks = callbacks;
2830 return SVN_NO_ERROR;
2833 static svn_error_t *
2834 ra_svn_get_inherited_props(svn_ra_session_t *session,
2835 apr_array_header_t **iprops,
2837 svn_revnum_t revision,
2838 apr_pool_t *result_pool,
2839 apr_pool_t *scratch_pool)
2841 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2842 svn_ra_svn_conn_t *conn = sess_baton->conn;
2843 apr_array_header_t *iproplist;
2844 svn_boolean_t iprop_capable;
2846 SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2847 SVN_RA_CAPABILITY_INHERITED_PROPS,
2850 /* If we don't support native iprop handling, use the implementation
2853 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2855 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2857 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2858 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2859 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2862 return SVN_NO_ERROR;
2865 static const svn_ra__vtable_t ra_svn_vtable = {
2867 ra_svn_get_description,
2872 ra_svn_get_session_url,
2873 ra_svn_get_latest_rev,
2874 ra_svn_get_dated_rev,
2875 ra_svn_change_rev_prop,
2876 ra_svn_rev_proplist,
2881 ra_svn_get_mergeinfo,
2890 ra_svn_get_repos_root,
2891 ra_svn_get_locations,
2892 ra_svn_get_location_segments,
2893 ra_svn_get_file_revs,
2899 ra_svn_has_capability,
2900 ra_svn_replay_range,
2901 ra_svn_get_deleted_rev,
2902 ra_svn_register_editor_shim_callbacks,
2903 ra_svn_get_inherited_props
2907 svn_ra_svn__init(const svn_version_t *loader_version,
2908 const svn_ra__vtable_t **vtable,
2911 static const svn_version_checklist_t checklist[] =
2913 { "svn_subr", svn_subr_version },
2914 { "svn_delta", svn_delta_version },
2918 SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2920 /* Simplified version check to make sure we can safely use the
2921 VTABLE parameter. The RA loader does a more exhaustive check. */
2922 if (loader_version->major != SVN_VER_MAJOR)
2924 return svn_error_createf
2925 (SVN_ERR_VERSION_MISMATCH, NULL,
2926 _("Unsupported RA loader version (%d) for ra_svn"),
2927 loader_version->major);
2930 *vtable = &ra_svn_vtable;
2932 #ifdef SVN_HAVE_SASL
2933 SVN_ERR(svn_ra_svn__sasl_init());
2936 return SVN_NO_ERROR;
2939 /* Compatibility wrapper for the 1.1 and before API. */
2940 #define NAME "ra_svn"
2941 #define DESCRIPTION RA_SVN_DESCRIPTION
2942 #define VTBL ra_svn_vtable
2943 #define INITFUNC svn_ra_svn__init
2944 #define COMPAT_INITFUNC svn_ra_svn_init
2945 #include "../libsvn_ra/wrapper_template.h"