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 apr_pool_t *iterpool;
1196 if (iproplist == NULL)
1198 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1199 capability we shouldn't be asking for inherited props, but if we
1200 did and the server sent back nothing then we'll want to handle
1202 *inherited_props = NULL;
1203 return SVN_NO_ERROR;
1206 *inherited_props = apr_array_make(
1207 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1209 iterpool = svn_pool_create(scratch_pool);
1211 for (i = 0; i < iproplist->nelts; i++)
1213 apr_array_header_t *iprop_list;
1214 char *parent_rel_path;
1216 apr_hash_index_t *hi;
1217 svn_prop_inherited_item_t *new_iprop =
1218 apr_palloc(result_pool, sizeof(*new_iprop));
1219 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1221 if (elt->kind != SVN_RA_SVN_LIST)
1222 return svn_error_create(
1223 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1224 _("Inherited proplist element not a list"));
1226 svn_pool_clear(iterpool);
1228 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1229 &parent_rel_path, &iprop_list));
1230 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1231 new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path);
1232 new_iprop->prop_hash = svn_hash__make(result_pool);
1233 for (hi = apr_hash_first(iterpool, iprops);
1235 hi = apr_hash_next(hi))
1237 const char *name = apr_hash_this_key(hi);
1238 svn_string_t *value = apr_hash_this_val(hi);
1239 svn_hash_sets(new_iprop->prop_hash,
1240 apr_pstrdup(result_pool, name),
1241 svn_string_dup(value, result_pool));
1243 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1246 svn_pool_destroy(iterpool);
1247 return SVN_NO_ERROR;
1250 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1251 svn_revnum_t rev, svn_stream_t *stream,
1252 svn_revnum_t *fetched_rev,
1256 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1257 svn_ra_svn_conn_t *conn = sess_baton->conn;
1258 apr_array_header_t *proplist;
1259 const char *expected_digest;
1260 svn_checksum_t *expected_checksum = NULL;
1261 svn_checksum_ctx_t *checksum_ctx;
1262 apr_pool_t *iterpool;
1264 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1265 (props != NULL), (stream != NULL)));
1266 SVN_ERR(handle_auth_request(sess_baton, pool));
1267 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1274 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1276 /* We're done if the contents weren't wanted. */
1278 return SVN_NO_ERROR;
1280 if (expected_digest)
1282 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1283 expected_digest, pool));
1284 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1287 /* Read the file's contents. */
1288 iterpool = svn_pool_create(pool);
1291 svn_ra_svn_item_t *item;
1293 svn_pool_clear(iterpool);
1294 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1295 if (item->kind != SVN_RA_SVN_STRING)
1296 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1297 _("Non-string as part of file contents"));
1298 if (item->u.string->len == 0)
1301 if (expected_checksum)
1302 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1303 item->u.string->len));
1305 SVN_ERR(svn_stream_write(stream, item->u.string->data,
1306 &item->u.string->len));
1308 svn_pool_destroy(iterpool);
1310 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1312 if (expected_checksum)
1314 svn_checksum_t *checksum;
1316 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1317 if (!svn_checksum_match(checksum, expected_checksum))
1318 return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1319 _("Checksum mismatch for '%s'"),
1323 return SVN_NO_ERROR;
1326 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1327 apr_hash_t **dirents,
1328 svn_revnum_t *fetched_rev,
1332 apr_uint32_t dirent_fields,
1335 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1336 svn_ra_svn_conn_t *conn = sess_baton->conn;
1337 apr_array_header_t *proplist, *dirlist;
1340 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1341 rev, (props != NULL), (dirents != NULL)));
1342 if (dirent_fields & SVN_DIRENT_KIND)
1343 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1344 if (dirent_fields & SVN_DIRENT_SIZE)
1345 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1346 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1347 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1348 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1349 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1350 if (dirent_fields & SVN_DIRENT_TIME)
1351 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1352 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1353 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1355 /* Always send the, nominally optional, want-iprops as "false" to
1356 workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1357 to see "true" if it is omitted. */
1358 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1360 SVN_ERR(handle_auth_request(sess_baton, pool));
1361 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1367 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1369 /* We're done if dirents aren't wanted. */
1371 return SVN_NO_ERROR;
1373 /* Interpret the directory list. */
1374 *dirents = svn_hash__make(pool);
1375 for (i = 0; i < dirlist->nelts; i++)
1377 const char *name, *kind, *cdate, *cauthor;
1378 svn_boolean_t has_props;
1379 svn_dirent_t *dirent;
1382 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1384 if (elt->kind != SVN_RA_SVN_LIST)
1385 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1386 _("Dirlist element not a list"));
1387 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1388 &name, &kind, &size, &has_props,
1389 &crev, &cdate, &cauthor));
1391 /* Nothing to sanitize here. Any multi-segment path is simply
1392 illegal in the hash returned by svn_ra_get_dir2. */
1393 if (strchr(name, '/'))
1394 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1395 _("Invalid directory entry name '%s'"),
1398 dirent = svn_dirent_create(pool);
1399 dirent->kind = svn_node_kind_from_word(kind);
1400 dirent->size = size;/* FIXME: svn_filesize_t */
1401 dirent->has_props = has_props;
1402 dirent->created_rev = crev;
1403 /* NOTE: the tuple's format string says CDATE may be NULL. But this
1404 function does not allow that. The server has always sent us some
1405 random date, however, so this just happens to work. But let's
1406 be wary of servers that are (improperly) fixed to send NULL.
1408 Note: they should NOT be "fixed" to send NULL, as that would break
1409 any older clients which received that NULL. But we may as well
1410 be defensive against a malicous server. */
1414 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1415 dirent->last_author = cauthor;
1416 svn_hash_sets(*dirents, name, dirent);
1419 return SVN_NO_ERROR;
1422 /* Converts a apr_uint64_t with values TRUE, FALSE or
1423 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1424 to a svn_tristate_t */
1425 static svn_tristate_t
1426 optbool_to_tristate(apr_uint64_t v)
1428 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
1429 return svn_tristate_true;
1431 return svn_tristate_false;
1433 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1436 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1437 server, which defaults to youngest. */
1438 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1439 svn_mergeinfo_catalog_t *catalog,
1440 const apr_array_header_t *paths,
1441 svn_revnum_t revision,
1442 svn_mergeinfo_inheritance_t inherit,
1443 svn_boolean_t include_descendants,
1446 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1447 svn_ra_svn_conn_t *conn = sess_baton->conn;
1449 apr_array_header_t *mergeinfo_tuple;
1450 svn_ra_svn_item_t *elt;
1453 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1454 for (i = 0; i < paths->nelts; i++)
1456 path = APR_ARRAY_IDX(paths, i, const char *);
1457 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1459 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1460 svn_inheritance_to_word(inherit),
1461 include_descendants));
1463 SVN_ERR(handle_auth_request(sess_baton, pool));
1464 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1467 if (mergeinfo_tuple->nelts > 0)
1469 *catalog = svn_hash__make(pool);
1470 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1472 svn_mergeinfo_t for_path;
1473 const char *to_parse;
1475 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1476 if (elt->kind != SVN_RA_SVN_LIST)
1477 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1478 _("Mergeinfo element is not a list"));
1479 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1481 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1482 /* Correct for naughty servers that send "relative" paths
1483 with leading slashes! */
1484 svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1488 return SVN_NO_ERROR;
1491 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1492 const svn_ra_reporter3_t **reporter,
1493 void **report_baton, svn_revnum_t rev,
1494 const char *target, svn_depth_t depth,
1495 svn_boolean_t send_copyfrom_args,
1496 svn_boolean_t ignore_ancestry,
1497 const svn_delta_editor_t *update_editor,
1500 apr_pool_t *scratch_pool)
1502 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1503 svn_ra_svn_conn_t *conn = sess_baton->conn;
1504 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1506 /* Tell the server we want to start an update. */
1507 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1508 depth, send_copyfrom_args,
1510 SVN_ERR(handle_auth_request(sess_baton, pool));
1512 /* Fetch a reporter for the caller to drive. The reporter will drive
1513 * update_editor upon finish_report(). */
1514 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1515 target, depth, reporter, report_baton));
1516 return SVN_NO_ERROR;
1519 static svn_error_t *
1520 ra_svn_switch(svn_ra_session_t *session,
1521 const svn_ra_reporter3_t **reporter,
1522 void **report_baton, svn_revnum_t rev,
1523 const char *target, svn_depth_t depth,
1524 const char *switch_url,
1525 svn_boolean_t send_copyfrom_args,
1526 svn_boolean_t ignore_ancestry,
1527 const svn_delta_editor_t *update_editor,
1529 apr_pool_t *result_pool,
1530 apr_pool_t *scratch_pool)
1532 apr_pool_t *pool = result_pool;
1533 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1534 svn_ra_svn_conn_t *conn = sess_baton->conn;
1535 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1537 /* Tell the server we want to start a switch. */
1538 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1540 send_copyfrom_args, ignore_ancestry));
1541 SVN_ERR(handle_auth_request(sess_baton, pool));
1543 /* Fetch a reporter for the caller to drive. The reporter will drive
1544 * update_editor upon finish_report(). */
1545 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1546 target, depth, reporter, report_baton));
1547 return SVN_NO_ERROR;
1550 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1551 const svn_ra_reporter3_t **reporter,
1552 void **report_baton,
1553 const char *target, svn_revnum_t rev,
1555 const svn_delta_editor_t *status_editor,
1556 void *status_baton, apr_pool_t *pool)
1558 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1559 svn_ra_svn_conn_t *conn = sess_baton->conn;
1560 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1562 /* Tell the server we want to start a status operation. */
1563 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1565 SVN_ERR(handle_auth_request(sess_baton, pool));
1567 /* Fetch a reporter for the caller to drive. The reporter will drive
1568 * status_editor upon finish_report(). */
1569 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1570 target, depth, reporter, report_baton));
1571 return SVN_NO_ERROR;
1574 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1575 const svn_ra_reporter3_t **reporter,
1576 void **report_baton,
1577 svn_revnum_t rev, const char *target,
1579 svn_boolean_t ignore_ancestry,
1580 svn_boolean_t text_deltas,
1581 const char *versus_url,
1582 const svn_delta_editor_t *diff_editor,
1583 void *diff_baton, apr_pool_t *pool)
1585 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1586 svn_ra_svn_conn_t *conn = sess_baton->conn;
1587 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1589 /* Tell the server we want to start a diff. */
1590 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1591 ignore_ancestry, versus_url,
1592 text_deltas, depth));
1593 SVN_ERR(handle_auth_request(sess_baton, pool));
1595 /* Fetch a reporter for the caller to drive. The reporter will drive
1596 * diff_editor upon finish_report(). */
1597 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1598 target, depth, reporter, report_baton));
1599 return SVN_NO_ERROR;
1603 static svn_error_t *
1604 perform_ra_svn_log(svn_error_t **outer_error,
1605 svn_ra_session_t *session,
1606 const apr_array_header_t *paths,
1607 svn_revnum_t start, svn_revnum_t end,
1609 svn_boolean_t discover_changed_paths,
1610 svn_boolean_t strict_node_history,
1611 svn_boolean_t include_merged_revisions,
1612 const apr_array_header_t *revprops,
1613 svn_log_entry_receiver_t receiver,
1614 void *receiver_baton,
1617 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1618 svn_ra_svn_conn_t *conn = sess_baton->conn;
1619 apr_pool_t *iterpool;
1624 svn_boolean_t want_custom_revprops;
1625 svn_boolean_t want_author = FALSE;
1626 svn_boolean_t want_message = FALSE;
1627 svn_boolean_t want_date = FALSE;
1630 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1633 for (i = 0; i < paths->nelts; i++)
1635 path = APR_ARRAY_IDX(paths, i, const char *);
1636 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1639 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1640 discover_changed_paths, strict_node_history,
1641 (apr_uint64_t) limit,
1642 include_merged_revisions));
1645 want_custom_revprops = FALSE;
1646 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1647 for (i = 0; i < revprops->nelts; i++)
1649 name = APR_ARRAY_IDX(revprops, i, char *);
1650 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1652 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1654 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1656 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1657 want_message = TRUE;
1659 want_custom_revprops = TRUE;
1661 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1665 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1669 want_message = TRUE;
1670 want_custom_revprops = TRUE;
1673 SVN_ERR(handle_auth_request(sess_baton, pool));
1675 /* Read the log messages. */
1676 iterpool = svn_pool_create(pool);
1679 apr_uint64_t has_children_param, invalid_revnum_param;
1680 apr_uint64_t has_subtractive_merge_param;
1681 svn_string_t *author, *date, *message;
1682 apr_array_header_t *cplist, *rplist;
1683 svn_log_entry_t *log_entry;
1684 svn_boolean_t has_children;
1685 svn_boolean_t subtractive_merge = FALSE;
1686 apr_uint64_t revprop_count;
1687 svn_ra_svn_item_t *item;
1691 svn_pool_clear(iterpool);
1692 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1693 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1695 if (item->kind != SVN_RA_SVN_LIST)
1696 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1697 _("Log entry not a list"));
1698 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1699 "lr(?s)(?s)(?s)?BBnl?B",
1700 &cplist, &rev, &author, &date,
1701 &message, &has_children_param,
1702 &invalid_revnum_param,
1703 &revprop_count, &rplist,
1704 &has_subtractive_merge_param));
1705 if (want_custom_revprops && rplist == NULL)
1707 /* Caller asked for custom revprops, but server is too old. */
1708 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1709 _("Server does not support custom revprops"
1713 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1714 has_children = FALSE;
1716 has_children = (svn_boolean_t) has_children_param;
1718 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1719 subtractive_merge = FALSE;
1721 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1723 /* Because the svn protocol won't let us send an invalid revnum, we have
1724 to recover that fact using the extra parameter. */
1725 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1726 && invalid_revnum_param)
1727 rev = SVN_INVALID_REVNUM;
1729 if (cplist->nelts > 0)
1731 /* Interpret the changed-paths list. */
1732 cphash = svn_hash__make(iterpool);
1733 for (i = 0; i < cplist->nelts; i++)
1735 svn_log_changed_path2_t *change;
1736 svn_string_t *cpath;
1737 const char *copy_path, *action, *kind_str;
1738 apr_uint64_t text_mods, prop_mods;
1739 svn_revnum_t copy_rev;
1740 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1743 if (elt->kind != SVN_RA_SVN_LIST)
1744 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1745 _("Changed-path entry not a list"));
1746 SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1747 &cpath, &action, ©_path,
1748 ©_rev, &kind_str,
1749 &text_mods, &prop_mods));
1751 if (!svn_fspath__is_canonical(cpath->data))
1753 cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1754 cpath->len = strlen(cpath->data);
1756 if (copy_path && !svn_fspath__is_canonical(copy_path))
1757 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1759 change = svn_log_changed_path2_create(iterpool);
1760 change->action = *action;
1761 change->copyfrom_path = copy_path;
1762 change->copyfrom_rev = copy_rev;
1763 change->node_kind = svn_node_kind_from_word(kind_str);
1764 change->text_modified = optbool_to_tristate(text_mods);
1765 change->props_modified = optbool_to_tristate(prop_mods);
1766 apr_hash_set(cphash, cpath->data, cpath->len, change);
1773 - Except if the server sends more than a >= 1 limit top level items
1774 - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1775 in an earlier invocation. */
1776 if (! (limit && (nest_level == 0) && (++nreceived > limit))
1780 log_entry = svn_log_entry_create(iterpool);
1782 log_entry->changed_paths = cphash;
1783 log_entry->changed_paths2 = cphash;
1784 log_entry->revision = rev;
1785 log_entry->has_children = has_children;
1786 log_entry->subtractive_merge = subtractive_merge;
1788 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1789 &log_entry->revprops));
1790 if (log_entry->revprops == NULL)
1791 log_entry->revprops = svn_hash__make(iterpool);
1793 if (author && want_author)
1794 svn_hash_sets(log_entry->revprops,
1795 SVN_PROP_REVISION_AUTHOR, author);
1796 if (date && want_date)
1797 svn_hash_sets(log_entry->revprops,
1798 SVN_PROP_REVISION_DATE, date);
1799 if (message && want_message)
1800 svn_hash_sets(log_entry->revprops,
1801 SVN_PROP_REVISION_LOG, message);
1803 err = receiver(receiver_baton, log_entry, iterpool);
1804 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1806 *outer_error = svn_error_trace(
1807 svn_error_compose_create(*outer_error, err));
1812 if (log_entry->has_children)
1816 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1818 SVN_ERR_ASSERT(nest_level);
1823 svn_pool_destroy(iterpool);
1825 /* Read the response. */
1826 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1829 static svn_error_t *
1830 ra_svn_log(svn_ra_session_t *session,
1831 const apr_array_header_t *paths,
1832 svn_revnum_t start, svn_revnum_t end,
1834 svn_boolean_t discover_changed_paths,
1835 svn_boolean_t strict_node_history,
1836 svn_boolean_t include_merged_revisions,
1837 const apr_array_header_t *revprops,
1838 svn_log_entry_receiver_t receiver,
1839 void *receiver_baton, apr_pool_t *pool)
1841 svn_error_t *outer_error = NULL;
1844 err = svn_error_trace(perform_ra_svn_log(&outer_error,
1848 discover_changed_paths,
1849 strict_node_history,
1850 include_merged_revisions,
1852 receiver, receiver_baton,
1854 return svn_error_trace(
1855 svn_error_compose_create(outer_error,
1861 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1862 const char *path, svn_revnum_t rev,
1863 svn_node_kind_t *kind, apr_pool_t *pool)
1865 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1866 svn_ra_svn_conn_t *conn = sess_baton->conn;
1867 const char *kind_word;
1869 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1870 SVN_ERR(handle_auth_request(sess_baton, pool));
1871 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1872 *kind = svn_node_kind_from_word(kind_word);
1873 return SVN_NO_ERROR;
1877 /* If ERR is a command not supported error, wrap it in a
1878 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1879 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1882 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1883 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1889 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1890 const char *path, svn_revnum_t rev,
1891 svn_dirent_t **dirent, apr_pool_t *pool)
1893 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1894 svn_ra_svn_conn_t *conn = sess_baton->conn;
1895 apr_array_header_t *list = NULL;
1896 svn_dirent_t *the_dirent;
1898 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1899 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1900 N_("'stat' not implemented")));
1901 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1909 const char *kind, *cdate, *cauthor;
1910 svn_boolean_t has_props;
1914 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1915 &kind, &size, &has_props,
1916 &crev, &cdate, &cauthor));
1918 the_dirent = svn_dirent_create(pool);
1919 the_dirent->kind = svn_node_kind_from_word(kind);
1920 the_dirent->size = size;/* FIXME: svn_filesize_t */
1921 the_dirent->has_props = has_props;
1922 the_dirent->created_rev = crev;
1923 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1924 the_dirent->last_author = cauthor;
1926 *dirent = the_dirent;
1929 return SVN_NO_ERROR;
1933 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1934 apr_hash_t **locations,
1936 svn_revnum_t peg_revision,
1937 const apr_array_header_t *location_revisions,
1940 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1941 svn_ra_svn_conn_t *conn = sess_baton->conn;
1942 svn_revnum_t revision;
1943 svn_boolean_t is_done;
1946 /* Transmit the parameters. */
1947 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1948 "get-locations", path, peg_revision));
1949 for (i = 0; i < location_revisions->nelts; i++)
1951 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1952 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1955 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
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-locations' not implemented")));
1961 /* Read the hash items. */
1963 *locations = apr_hash_make(pool);
1966 svn_ra_svn_item_t *item;
1967 const char *ret_path;
1969 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1970 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1972 else if (item->kind != SVN_RA_SVN_LIST)
1973 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1974 _("Location entry not a list"));
1977 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1978 &revision, &ret_path));
1979 ret_path = svn_fspath__canonicalize(ret_path, pool);
1980 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1982 sizeof(revision), ret_path);
1986 /* Read the response. This is so the server would have a chance to
1987 * report an error. */
1988 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1991 static svn_error_t *
1992 ra_svn_get_location_segments(svn_ra_session_t *session,
1994 svn_revnum_t peg_revision,
1995 svn_revnum_t start_rev,
1996 svn_revnum_t end_rev,
1997 svn_location_segment_receiver_t receiver,
1998 void *receiver_baton,
2001 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2002 svn_ra_svn_conn_t *conn = sess_baton->conn;
2003 svn_boolean_t is_done;
2004 apr_pool_t *iterpool = svn_pool_create(pool);
2006 /* Transmit the parameters. */
2007 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2008 "get-location-segments",
2009 path, peg_revision, start_rev, end_rev));
2011 /* Servers before 1.5 don't support this command. Check for this here. */
2012 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2013 N_("'get-location-segments'"
2014 " not implemented")));
2016 /* Parse the response. */
2020 svn_revnum_t range_start, range_end;
2021 svn_ra_svn_item_t *item;
2022 const char *ret_path;
2024 svn_pool_clear(iterpool);
2025 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2026 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2028 else if (item->kind != SVN_RA_SVN_LIST)
2029 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2030 _("Location segment entry not a list"));
2033 svn_location_segment_t *segment = apr_pcalloc(iterpool,
2035 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2036 &range_start, &range_end, &ret_path));
2037 if (! (SVN_IS_VALID_REVNUM(range_start)
2038 && SVN_IS_VALID_REVNUM(range_end)))
2039 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2040 _("Expected valid revision range"));
2042 ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2043 segment->path = ret_path;
2044 segment->range_start = range_start;
2045 segment->range_end = range_end;
2046 SVN_ERR(receiver(segment, receiver_baton, iterpool));
2049 svn_pool_destroy(iterpool);
2051 /* Read the response. This is so the server would have a chance to
2052 * report an error. */
2053 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2055 return SVN_NO_ERROR;
2058 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2060 svn_revnum_t start, svn_revnum_t end,
2061 svn_boolean_t include_merged_revisions,
2062 svn_file_rev_handler_t handler,
2063 void *handler_baton, apr_pool_t *pool)
2065 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2066 apr_pool_t *rev_pool, *chunk_pool;
2067 svn_boolean_t has_txdelta;
2068 svn_boolean_t had_revision = FALSE;
2070 /* One sub-pool for each revision and one for each txdelta chunk.
2071 Note that the rev_pool must live during the following txdelta. */
2072 rev_pool = svn_pool_create(pool);
2073 chunk_pool = svn_pool_create(pool);
2075 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2077 include_merged_revisions));
2079 /* Servers before 1.1 don't support this command. Check for this here. */
2080 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2081 N_("'get-file-revs' not implemented")));
2085 apr_array_header_t *rev_proplist, *proplist;
2086 apr_uint64_t merged_rev_param;
2087 apr_array_header_t *props;
2088 svn_ra_svn_item_t *item;
2089 apr_hash_t *rev_props;
2092 svn_boolean_t merged_rev;
2093 svn_txdelta_window_handler_t d_handler;
2096 svn_pool_clear(rev_pool);
2097 svn_pool_clear(chunk_pool);
2098 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2099 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2101 /* Either we've got a correct revision or we will error out below. */
2102 had_revision = TRUE;
2103 if (item->kind != SVN_RA_SVN_LIST)
2104 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2105 _("Revision entry not a list"));
2107 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2108 "crll?B", &p, &rev, &rev_proplist,
2109 &proplist, &merged_rev_param));
2110 p = svn_fspath__canonicalize(p, rev_pool);
2111 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2112 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2113 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2116 merged_rev = (svn_boolean_t) merged_rev_param;
2118 /* Get the first delta chunk so we know if there is a delta. */
2119 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2120 if (item->kind != SVN_RA_SVN_STRING)
2121 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2122 _("Text delta chunk not a string"));
2123 has_txdelta = item->u.string->len > 0;
2125 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2126 has_txdelta ? &d_handler : NULL, &d_baton,
2129 /* Process the text delta if any. */
2132 svn_stream_t *stream;
2134 if (d_handler && d_handler != svn_delta_noop_window_handler)
2135 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2139 while (item->u.string->len > 0)
2143 size = item->u.string->len;
2145 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2146 svn_pool_clear(chunk_pool);
2148 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2150 if (item->kind != SVN_RA_SVN_STRING)
2151 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2152 _("Text delta chunk not a string"));
2155 SVN_ERR(svn_stream_close(stream));
2159 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2161 /* Return error if we didn't get any revisions. */
2163 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2164 _("The get-file-revs command didn't return "
2167 svn_pool_destroy(chunk_pool);
2168 svn_pool_destroy(rev_pool);
2170 return SVN_NO_ERROR;
2173 /* For each path in PATH_REVS, send a 'lock' command to the server.
2174 Used with 1.2.x series servers which support locking, but of only
2175 one path at a time. ra_svn_lock(), which supports 'lock-many'
2176 is now the default. See svn_ra_lock() docstring for interface details. */
2177 static svn_error_t *ra_svn_lock_compat(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_array_header_t *list;
2188 apr_hash_index_t *hi;
2189 apr_pool_t *iterpool = svn_pool_create(pool);
2191 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2197 svn_revnum_t *revnum;
2198 svn_error_t *err, *callback_err = NULL;
2200 svn_pool_clear(iterpool);
2202 apr_hash_this(hi, &key, NULL, &val);
2206 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2207 steal_lock, *revnum));
2209 /* Servers before 1.2 doesn't support locking. Check this here. */
2210 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2211 N_("Server doesn't support "
2212 "the lock command")));
2214 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2217 SVN_ERR(parse_lock(list, iterpool, &lock));
2219 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2223 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2226 svn_error_clear(err);
2229 return callback_err;
2232 svn_pool_destroy(iterpool);
2234 return SVN_NO_ERROR;
2237 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2238 Used with 1.2.x series servers which support unlocking, but of only
2239 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
2240 now the default. See svn_ra_unlock() docstring for interface details. */
2241 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2242 apr_hash_t *path_tokens,
2243 svn_boolean_t break_lock,
2244 svn_ra_lock_callback_t lock_func,
2248 svn_ra_svn__session_baton_t *sess = session->priv;
2249 svn_ra_svn_conn_t* conn = sess->conn;
2250 apr_hash_index_t *hi;
2251 apr_pool_t *iterpool = svn_pool_create(pool);
2253 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2259 svn_error_t *err, *callback_err = NULL;
2261 svn_pool_clear(iterpool);
2263 apr_hash_this(hi, &key, NULL, &val);
2265 if (strcmp(val, "") != 0)
2270 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2273 /* Servers before 1.2 don't support locking. Check this here. */
2274 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2275 N_("Server doesn't support the unlock "
2278 err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2280 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2284 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2286 svn_error_clear(err);
2289 return callback_err;
2292 svn_pool_destroy(iterpool);
2294 return SVN_NO_ERROR;
2297 /* Tell the server to lock all paths in PATH_REVS.
2298 See svn_ra_lock() for interface details. */
2299 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2300 apr_hash_t *path_revs,
2301 const char *comment,
2302 svn_boolean_t steal_lock,
2303 svn_ra_lock_callback_t lock_func,
2307 svn_ra_svn__session_baton_t *sess = session->priv;
2308 svn_ra_svn_conn_t *conn = sess->conn;
2309 apr_hash_index_t *hi;
2311 apr_pool_t *iterpool = svn_pool_create(pool);
2313 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2314 comment, steal_lock));
2316 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2321 svn_revnum_t *revnum;
2323 svn_pool_clear(iterpool);
2324 apr_hash_this(hi, &key, NULL, &val);
2328 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2331 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2333 err = handle_auth_request(sess, pool);
2335 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2337 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2339 svn_error_clear(err);
2340 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2341 lock_func, lock_baton, pool);
2347 /* Loop over responses to get lock information. */
2348 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2350 svn_ra_svn_item_t *elt;
2353 svn_error_t *callback_err;
2356 apr_array_header_t *list;
2358 apr_hash_this(hi, &key, NULL, NULL);
2361 svn_pool_clear(iterpool);
2362 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2364 /* The server might have encountered some sort of fatal error in
2365 the middle of the request list. If this happens, it will
2366 transmit "done" to end the lock-info early, and then the
2367 overall command response will talk about the fatal error. */
2368 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2371 if (elt->kind != SVN_RA_SVN_LIST)
2372 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2373 _("Lock response not a list"));
2375 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2378 if (strcmp(status, "failure") == 0)
2379 err = svn_ra_svn__handle_failure_status(list, iterpool);
2380 else if (strcmp(status, "success") == 0)
2382 SVN_ERR(parse_lock(list, iterpool, &lock));
2386 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2387 _("Unknown status for lock command"));
2390 callback_err = lock_func(lock_baton, path, TRUE,
2394 callback_err = SVN_NO_ERROR;
2396 svn_error_clear(err);
2399 return callback_err;
2402 /* If we didn't break early above, and the whole hash was traversed,
2403 read the final "done" from the server. */
2406 svn_ra_svn_item_t *elt;
2408 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2409 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2410 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2411 _("Didn't receive end marker for lock "
2415 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2417 svn_pool_destroy(iterpool);
2419 return SVN_NO_ERROR;
2422 /* Tell the server to unlock all paths in PATH_TOKENS.
2423 See svn_ra_unlock() for interface details. */
2424 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2425 apr_hash_t *path_tokens,
2426 svn_boolean_t break_lock,
2427 svn_ra_lock_callback_t lock_func,
2431 svn_ra_svn__session_baton_t *sess = session->priv;
2432 svn_ra_svn_conn_t *conn = sess->conn;
2433 apr_hash_index_t *hi;
2434 apr_pool_t *iterpool = svn_pool_create(pool);
2438 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2441 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2447 svn_pool_clear(iterpool);
2448 apr_hash_this(hi, &key, NULL, &val);
2451 if (strcmp(val, "") != 0)
2456 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2459 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2461 err = handle_auth_request(sess, pool);
2463 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2466 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2468 svn_error_clear(err);
2469 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2476 /* Loop over responses to unlock files. */
2477 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2479 svn_ra_svn_item_t *elt;
2481 svn_error_t *callback_err;
2483 apr_array_header_t *list;
2485 svn_pool_clear(iterpool);
2487 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2489 /* The server might have encountered some sort of fatal error in
2490 the middle of the request list. If this happens, it will
2491 transmit "done" to end the lock-info early, and then the
2492 overall command response will talk about the fatal error. */
2493 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2496 apr_hash_this(hi, &key, NULL, NULL);
2499 if (elt->kind != SVN_RA_SVN_LIST)
2500 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2501 _("Unlock response not a list"));
2503 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2506 if (strcmp(status, "failure") == 0)
2507 err = svn_ra_svn__handle_failure_status(list, iterpool);
2508 else if (strcmp(status, "success") == 0)
2510 SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2514 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2515 _("Unknown status for unlock command"));
2518 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2521 callback_err = SVN_NO_ERROR;
2523 svn_error_clear(err);
2526 return callback_err;
2529 /* If we didn't break early above, and the whole hash was traversed,
2530 read the final "done" from the server. */
2533 svn_ra_svn_item_t *elt;
2535 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2536 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2537 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2538 _("Didn't receive end marker for unlock "
2542 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2544 svn_pool_destroy(iterpool);
2546 return SVN_NO_ERROR;
2549 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2554 svn_ra_svn__session_baton_t *sess = session->priv;
2555 svn_ra_svn_conn_t* conn = sess->conn;
2556 apr_array_header_t *list;
2558 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2560 /* Servers before 1.2 doesn't support locking. Check this here. */
2561 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2562 N_("Server doesn't support the get-lock "
2565 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2567 SVN_ERR(parse_lock(list, pool, lock));
2571 return SVN_NO_ERROR;
2574 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2575 to prevent a dependency cycle. */
2576 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2577 const char **rel_path,
2581 const char *root_url;
2583 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2584 *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2586 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2587 _("'%s' isn't a child of repository root "
2590 return SVN_NO_ERROR;
2593 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2599 svn_ra_svn__session_baton_t *sess = session->priv;
2600 svn_ra_svn_conn_t* conn = sess->conn;
2601 apr_array_header_t *list;
2602 const char *full_url, *abs_path;
2605 /* Figure out the repository abspath from PATH. */
2606 full_url = svn_path_url_add_component2(sess->url, path, pool);
2607 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2608 abs_path = svn_fspath__canonicalize(abs_path, pool);
2610 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2612 /* Servers before 1.2 doesn't support locking. Check this here. */
2613 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2614 N_("Server doesn't support the get-lock "
2617 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2619 *locks = apr_hash_make(pool);
2621 for (i = 0; i < list->nelts; ++i)
2624 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2626 if (elt->kind != SVN_RA_SVN_LIST)
2627 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2628 _("Lock element not a list"));
2629 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2631 /* Filter out unwanted paths. Since Subversion only allows
2632 locks on files, we can treat depth=immediates the same as
2633 depth=files for filtering purposes. Meaning, we'll keep
2636 a) its path is the very path we queried, or
2637 b) we've asked for a fully recursive answer, or
2638 c) we've asked for depth=files or depth=immediates, and this
2639 lock is on an immediate child of our query path.
2641 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2643 svn_hash_sets(*locks, lock->path, lock);
2645 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2647 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2648 if (relpath && (svn_path_component_count(relpath) == 1))
2649 svn_hash_sets(*locks, lock->path, lock);
2653 return SVN_NO_ERROR;
2657 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2658 svn_revnum_t revision,
2659 svn_revnum_t low_water_mark,
2660 svn_boolean_t send_deltas,
2661 const svn_delta_editor_t *editor,
2665 svn_ra_svn__session_baton_t *sess = session->priv;
2667 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2668 low_water_mark, send_deltas));
2670 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2671 N_("Server doesn't support the replay "
2674 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2677 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2681 static svn_error_t *
2682 ra_svn_replay_range(svn_ra_session_t *session,
2683 svn_revnum_t start_revision,
2684 svn_revnum_t end_revision,
2685 svn_revnum_t low_water_mark,
2686 svn_boolean_t send_deltas,
2687 svn_ra_replay_revstart_callback_t revstart_func,
2688 svn_ra_replay_revfinish_callback_t revfinish_func,
2692 svn_ra_svn__session_baton_t *sess = session->priv;
2693 apr_pool_t *iterpool;
2695 svn_boolean_t drive_aborted = FALSE;
2697 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2698 start_revision, end_revision,
2699 low_water_mark, send_deltas));
2701 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2702 N_("Server doesn't support the "
2703 "replay-range command")));
2705 iterpool = svn_pool_create(pool);
2706 for (rev = start_revision; rev <= end_revision; rev++)
2708 const svn_delta_editor_t *editor;
2710 apr_hash_t *rev_props;
2712 apr_array_header_t *list;
2714 svn_pool_clear(iterpool);
2716 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2717 "wl", &word, &list));
2718 if (strcmp(word, "revprops") != 0)
2719 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2720 _("Expected 'revprops', found '%s'"),
2723 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2725 SVN_ERR(revstart_func(rev, replay_baton,
2726 &editor, &edit_baton,
2729 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2731 &drive_aborted, TRUE));
2732 /* If drive_editor2() aborted the commit, do NOT try to call
2733 revfinish_func and commit the transaction! */
2734 if (drive_aborted) {
2735 svn_pool_destroy(iterpool);
2736 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2737 _("Error while replaying commit"));
2739 SVN_ERR(revfinish_func(rev, replay_baton,
2744 svn_pool_destroy(iterpool);
2746 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2750 static svn_error_t *
2751 ra_svn_has_capability(svn_ra_session_t *session,
2753 const char *capability,
2756 svn_ra_svn__session_baton_t *sess = session->priv;
2757 static const char* capabilities[][2] =
2759 /* { ra capability string, svn:// wire capability string} */
2760 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2761 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2762 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2763 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2764 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2765 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2766 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2767 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2768 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2769 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2770 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2772 {NULL, NULL} /* End of list marker */
2778 for (i = 0; capabilities[i][0]; i++)
2780 if (strcmp(capability, capabilities[i][0]) == 0)
2782 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2783 return SVN_NO_ERROR;
2787 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2788 _("Don't know anything about capability '%s'"),
2792 static svn_error_t *
2793 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2795 svn_revnum_t peg_revision,
2796 svn_revnum_t end_revision,
2797 svn_revnum_t *revision_deleted,
2801 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2802 svn_ra_svn_conn_t *conn = sess_baton->conn;
2804 /* Transmit the parameters. */
2805 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2806 peg_revision, end_revision));
2808 /* Servers before 1.6 don't support this command. Check for this here. */
2809 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2810 N_("'get-deleted-rev' not implemented")));
2812 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2816 static svn_error_t *
2817 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2818 svn_delta_shim_callbacks_t *callbacks)
2820 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2821 svn_ra_svn_conn_t *conn = sess_baton->conn;
2823 conn->shim_callbacks = callbacks;
2825 return SVN_NO_ERROR;
2828 static svn_error_t *
2829 ra_svn_get_inherited_props(svn_ra_session_t *session,
2830 apr_array_header_t **iprops,
2832 svn_revnum_t revision,
2833 apr_pool_t *result_pool,
2834 apr_pool_t *scratch_pool)
2836 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2837 svn_ra_svn_conn_t *conn = sess_baton->conn;
2838 apr_array_header_t *iproplist;
2839 svn_boolean_t iprop_capable;
2841 SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2842 SVN_RA_CAPABILITY_INHERITED_PROPS,
2845 /* If we don't support native iprop handling, use the implementation
2848 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2850 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2852 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2853 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2854 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2857 return SVN_NO_ERROR;
2860 static const svn_ra__vtable_t ra_svn_vtable = {
2862 ra_svn_get_description,
2867 ra_svn_get_session_url,
2868 ra_svn_get_latest_rev,
2869 ra_svn_get_dated_rev,
2870 ra_svn_change_rev_prop,
2871 ra_svn_rev_proplist,
2876 ra_svn_get_mergeinfo,
2885 ra_svn_get_repos_root,
2886 ra_svn_get_locations,
2887 ra_svn_get_location_segments,
2888 ra_svn_get_file_revs,
2894 ra_svn_has_capability,
2895 ra_svn_replay_range,
2896 ra_svn_get_deleted_rev,
2897 ra_svn_register_editor_shim_callbacks,
2898 ra_svn_get_inherited_props
2902 svn_ra_svn__init(const svn_version_t *loader_version,
2903 const svn_ra__vtable_t **vtable,
2906 static const svn_version_checklist_t checklist[] =
2908 { "svn_subr", svn_subr_version },
2909 { "svn_delta", svn_delta_version },
2913 SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2915 /* Simplified version check to make sure we can safely use the
2916 VTABLE parameter. The RA loader does a more exhaustive check. */
2917 if (loader_version->major != SVN_VER_MAJOR)
2919 return svn_error_createf
2920 (SVN_ERR_VERSION_MISMATCH, NULL,
2921 _("Unsupported RA loader version (%d) for ra_svn"),
2922 loader_version->major);
2925 *vtable = &ra_svn_vtable;
2927 #ifdef SVN_HAVE_SASL
2928 SVN_ERR(svn_ra_svn__sasl_init());
2931 return SVN_NO_ERROR;
2934 /* Compatibility wrapper for the 1.1 and before API. */
2935 #define NAME "ra_svn"
2936 #define DESCRIPTION RA_SVN_DESCRIPTION
2937 #define VTBL ra_svn_vtable
2938 #define INITFUNC svn_ra_svn__init
2939 #define COMPAT_INITFUNC svn_ra_svn_init
2940 #include "../libsvn_ra/wrapper_template.h"