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"
49 #include "svn_ctype.h"
51 #include "svn_private_config.h"
53 #include "private/svn_fspath.h"
54 #include "private/svn_subr_private.h"
56 #include "../libsvn_ra/ra_loader.h"
61 #define DO_AUTH svn_ra_svn__do_cyrus_auth
63 #define DO_AUTH svn_ra_svn__do_internal_auth
66 /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
67 whatever reason) deems svn_depth_immediates as non-recursive, which
68 is ... kinda true, but not true enough for our purposes. We need
69 our requested recursion level to be *at least* as recursive as the
70 real depth we're looking for.
72 #define DEPTH_TO_RECURSE(d) \
73 ((d) == svn_depth_unknown || (d) > svn_depth_files)
75 typedef struct ra_svn_commit_callback_baton_t {
76 svn_ra_svn__session_baton_t *sess_baton;
78 svn_revnum_t *new_rev;
79 svn_commit_callback2_t callback;
81 } ra_svn_commit_callback_baton_t;
83 typedef struct ra_svn_reporter_baton_t {
84 svn_ra_svn__session_baton_t *sess_baton;
85 svn_ra_svn_conn_t *conn;
87 const svn_delta_editor_t *editor;
89 } ra_svn_reporter_baton_t;
91 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
93 static void parse_tunnel(const char *url, const char **tunnel,
98 if (strncasecmp(url, "svn", 3) != 0)
102 /* Get the tunnel specification, if any. */
108 p = strchr(url, ':');
111 *tunnel = apr_pstrmemdup(pool, url, p - url);
115 static svn_error_t *make_connection(const char *hostname, unsigned short port,
116 apr_socket_t **sock, apr_pool_t *pool)
120 int family = APR_INET;
122 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
123 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
124 create IPV6 sockets. */
127 #ifdef MAX_SECS_TO_LINGER
128 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
130 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
131 APR_PROTO_TCP, pool);
135 apr_socket_close(*sock);
140 /* Resolve the hostname. */
141 status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
143 return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
145 /* Iterate through the returned list of addresses attempting to
146 * connect to each in turn. */
149 /* Create the socket. */
150 #ifdef MAX_SECS_TO_LINGER
151 /* ### old APR interface */
152 status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
154 status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
157 if (status == APR_SUCCESS)
159 status = apr_socket_connect(*sock, sa);
160 if (status != APR_SUCCESS)
161 apr_socket_close(*sock);
165 while (status != APR_SUCCESS && sa);
168 return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
171 /* Enable TCP keep-alives on the socket so we time out when
172 * the connection breaks due to network-layer problems.
173 * If the peer has dropped the connection due to a network partition
174 * or a crash, or if the peer no longer considers the connection
175 * valid because we are behind a NAT and our public IP has changed,
176 * it will respond to the keep-alive probe with a RST instead of an
177 * acknowledgment segment, which will cause svn to abort the session
178 * even while it is currently blocked waiting for data from the peer.
179 * See issue #3347. */
180 status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
183 /* It's not a fatal error if we cannot enable keep-alives. */
189 /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
190 property diffs in LIST, received from the server. */
191 static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
193 apr_array_header_t **diffs)
197 *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
199 for (i = 0; i < list->nelts; i++)
202 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
204 if (elt->kind != SVN_RA_SVN_LIST)
205 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
206 _("Prop diffs element not a list"));
207 prop = apr_array_push(*diffs);
208 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
214 /* Parse a lockdesc, provided in LIST as specified by the protocol into
215 LOCK, allocated in POOL. */
216 static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
219 const char *cdate, *edate;
220 *lock = svn_lock_create(pool);
221 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
222 &(*lock)->token, &(*lock)->owner,
223 &(*lock)->comment, &cdate, &edate));
224 (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
225 SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
227 SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
231 /* --- AUTHENTICATION ROUTINES --- */
233 svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
235 const char *mech, const char *mech_arg)
237 return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg));
240 static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
243 svn_ra_svn_conn_t *conn = sess->conn;
244 apr_array_header_t *mechlist;
247 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
248 if (mechlist->nelts == 0)
250 return DO_AUTH(sess, mechlist, realm, pool);
253 /* --- REPORTER IMPLEMENTATION --- */
255 static svn_error_t *ra_svn_set_path(void *baton, const char *path,
258 svn_boolean_t start_empty,
259 const char *lock_token,
262 ra_svn_reporter_baton_t *b = baton;
264 SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
265 start_empty, lock_token, depth));
269 static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
272 ra_svn_reporter_baton_t *b = baton;
274 SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
278 static svn_error_t *ra_svn_link_path(void *baton, const char *path,
282 svn_boolean_t start_empty,
283 const char *lock_token,
286 ra_svn_reporter_baton_t *b = baton;
288 SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
289 start_empty, lock_token, depth));
293 static svn_error_t *ra_svn_finish_report(void *baton,
296 ra_svn_reporter_baton_t *b = baton;
298 SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
299 SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
300 SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
302 SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
306 static svn_error_t *ra_svn_abort_report(void *baton,
309 ra_svn_reporter_baton_t *b = baton;
311 SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
315 static svn_ra_reporter3_t ra_svn_reporter = {
319 ra_svn_finish_report,
323 /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
324 * EDITOR/EDIT_BATON when it gets the finish_report() call.
326 * Allocate the new reporter in POOL.
329 ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
331 const svn_delta_editor_t *editor,
335 const svn_ra_reporter3_t **reporter,
338 ra_svn_reporter_baton_t *b;
339 const svn_delta_editor_t *filter_editor;
342 /* We can skip the depth filtering when the user requested
343 depth_files or depth_infinity because the server will
344 transmit the right stuff anyway. */
345 if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
346 && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
348 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
350 editor, edit_baton, depth,
353 editor = filter_editor;
354 edit_baton = filter_baton;
357 b = apr_palloc(pool, sizeof(*b));
358 b->sess_baton = sess_baton;
359 b->conn = sess_baton->conn;
362 b->edit_baton = edit_baton;
364 *reporter = &ra_svn_reporter;
370 /* --- RA LAYER IMPLEMENTATION --- */
372 /* (Note: *ARGV_P is an output parameter.) */
373 static svn_error_t *find_tunnel_agent(const char *tunnel,
374 const char *hostinfo,
375 const char ***argv_p,
376 apr_hash_t *config, apr_pool_t *pool)
379 const char *val, *var, *cmd;
386 /* Look up the tunnel specification in config. */
387 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
388 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
390 /* We have one predefined tunnel scheme, if it isn't overridden by config. */
391 if (!val && strcmp(tunnel, "ssh") == 0)
393 /* Killing the tunnel agent with SIGTERM leads to unsightly
394 * stderr output from ssh, unless we pass -q.
395 * The "-q" option to ssh is widely supported: all versions of
396 * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
397 * versions have it too. If the user is using some other ssh
398 * implementation that doesn't accept it, they can override it
399 * in the [tunnels] section of the config. */
400 val = "$SVN_SSH ssh -q --";
404 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
405 _("Undefined tunnel scheme '%s'"), tunnel);
407 /* If the scheme definition begins with "$varname", it means there
408 * is an environment variable which can override the command. */
412 len = strcspn(val, " ");
413 var = apr_pstrmemdup(pool, val, len);
421 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
422 _("Tunnel scheme %s requires environment "
423 "variable %s to be defined"), tunnel,
430 /* Tokenize the command into a list of arguments. */
431 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
432 if (status != APR_SUCCESS)
433 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
435 /* Calc number of the fixed arguments. */
436 for (n = 0; cmd_argv[n] != NULL; n++)
439 argv = apr_palloc(pool, (n + 4) * sizeof(char *));
441 /* Append the fixed arguments to the result. */
442 for (n = 0; cmd_argv[n] != NULL; n++)
443 argv[n] = cmd_argv[n];
445 argv[n++] = hostinfo;
446 argv[n++] = "svnserve";
454 /* This function handles any errors which occur in the child process
455 * created for a tunnel agent. We write the error out as a command
456 * failure; the code in ra_svn_open() to read the server's greeting
457 * will see the error and return it to the caller. */
458 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
461 svn_ra_svn_conn_t *conn;
462 apr_file_t *in_file, *out_file;
463 svn_stream_t *in_stream, *out_stream;
466 if (apr_file_open_stdin(&in_file, pool)
467 || apr_file_open_stdout(&out_file, pool))
470 in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool);
471 out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool);
473 conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream,
474 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
476 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
477 svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
478 svn_error_clear(err);
479 svn_error_clear(svn_ra_svn__flush(conn, pool));
482 /* (Note: *CONN is an output parameter.) */
483 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
488 apr_procattr_t *attr;
491 status = apr_procattr_create(&attr, pool);
492 if (status == APR_SUCCESS)
493 status = apr_procattr_io_set(attr, 1, 1, 0);
494 if (status == APR_SUCCESS)
495 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
496 if (status == APR_SUCCESS)
497 status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
498 proc = apr_palloc(pool, sizeof(*proc));
499 if (status == APR_SUCCESS)
500 status = apr_proc_create(proc, *args, args, NULL, attr, pool);
501 if (status != APR_SUCCESS)
502 return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
503 svn_error_wrap_apr(status,
504 _("Can't create tunnel")), NULL);
506 /* Arrange for the tunnel agent to get a SIGTERM on pool
507 * cleanup. This is a little extreme, but the alternatives
508 * weren't working out.
510 * Closing the pipes and waiting for the process to die
511 * was prone to mysterious hangs which are difficult to
512 * diagnose (e.g. svnserve dumps core due to unrelated bug;
513 * sshd goes into zombie state; ssh connection is never
514 * closed; ssh never terminates).
515 * See also the long dicussion in issue #2580 if you really
516 * want to know various reasons for these problems and
517 * the different opinions on this issue.
519 * On Win32, APR does not support KILL_ONLY_ONCE. It only has
520 * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
521 * KILL_ALWAYS, which immediately calls TerminateProcess().
522 * This instantly kills the tunnel, leaving sshd and svnserve
523 * on a remote machine running indefinitely. These processes
524 * accumulate. The problem is most often seen with a fast client
525 * machine and a modest internet connection, as the tunnel
526 * is killed before being able to gracefully complete the
527 * session. In that case, svn is unusable 100% of the time on
528 * the windows machine. Thus, on Win32, we use KILL_NEVER and
529 * take the lesser of two evils.
532 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
534 apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
537 /* APR pipe objects inherit by default. But we don't want the
538 * tunnel agent's pipes held open by future child processes
539 * (such as other ra_svn sessions), so turn that off. */
540 apr_file_inherit_unset(proc->in);
541 apr_file_inherit_unset(proc->out);
543 /* Guard against dotfile output to stdout on the server. */
544 *conn = svn_ra_svn_create_conn4(NULL,
545 svn_stream_from_aprfile2(proc->out, FALSE,
547 svn_stream_from_aprfile2(proc->in, FALSE,
549 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
551 err = svn_ra_svn__skip_leading_garbage(*conn, pool);
553 return svn_error_quick_wrap(
555 _("To better debug SSH connection problems, remove the -q "
556 "option from 'ssh' in the [tunnels] section of your "
557 "Subversion configuration file."));
562 /* Parse URL inot URI, validating it and setting the default port if none
563 was given. Allocate the URI fileds out of POOL. */
564 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
567 apr_status_t apr_err;
569 apr_err = apr_uri_parse(pool, url, uri);
572 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
573 _("Illegal svn repository URL '%s'"), url);
578 /* This structure is used as a baton for the pool cleanup function to
579 store tunnel parameters used by the close-tunnel callback. */
580 struct tunnel_data_t {
581 void *tunnel_context;
583 svn_ra_close_tunnel_func_t close_tunnel;
584 svn_stream_t *request;
585 svn_stream_t *response;
588 /* Pool cleanup function that invokes the close-tunnel callback. */
589 static apr_status_t close_tunnel_cleanup(void *baton)
591 const struct tunnel_data_t *const td = baton;
593 if (td->close_tunnel)
594 td->close_tunnel(td->tunnel_context, td->tunnel_baton);
596 svn_error_clear(svn_stream_close(td->request));
598 /* We might have one stream to use for both request and response! */
599 if (td->request != td->response)
600 svn_error_clear(svn_stream_close(td->response));
602 return APR_SUCCESS; /* ignored */
605 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
606 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
607 are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL,
608 it is the name of the tunnel type parsed from the URL scheme.
609 If TUNNEL_ARGV is not NULL, it points to a program argument list to use
610 when invoking the tunnel agent.
612 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
614 const apr_uri_t *uri,
615 const char *tunnel_name,
616 const char **tunnel_argv,
618 const svn_ra_callbacks2_t *callbacks,
619 void *callbacks_baton,
620 svn_auth_baton_t *auth_baton,
621 apr_pool_t *result_pool,
622 apr_pool_t *scratch_pool)
624 svn_ra_svn__session_baton_t *sess;
625 svn_ra_svn_conn_t *conn;
627 apr_uint64_t minver, maxver;
628 apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
629 const char *client_string = NULL;
630 apr_pool_t *pool = result_pool;
632 sess = apr_palloc(pool, sizeof(*sess));
634 sess->is_tunneled = (tunnel_name != NULL);
635 sess->url = apr_pstrdup(pool, url);
636 sess->user = uri->user;
637 sess->hostname = uri->hostname;
638 sess->tunnel_name = tunnel_name;
639 sess->tunnel_argv = tunnel_argv;
640 sess->callbacks = callbacks;
641 sess->callbacks_baton = callbacks_baton;
642 sess->bytes_read = sess->bytes_written = 0;
643 sess->auth_baton = auth_baton;
646 SVN_ERR(svn_config_copy_config(&sess->config, config, pool));
652 sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>",
654 uri->hostname, uri->port);
657 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
660 struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td));
662 td->tunnel_baton = callbacks->tunnel_baton;
663 td->close_tunnel = NULL;
665 SVN_ERR(callbacks->open_tunnel_func(
666 &td->request, &td->response,
667 &td->close_tunnel, &td->tunnel_context,
668 callbacks->tunnel_baton, tunnel_name,
669 uri->user, uri->hostname, uri->port,
670 callbacks->cancel_func, callbacks_baton,
673 apr_pool_cleanup_register(pool, td, close_tunnel_cleanup,
674 apr_pool_cleanup_null);
676 conn = svn_ra_svn_create_conn4(NULL, td->response, td->request,
677 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
679 SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool));
684 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
685 uri->port ? uri->port : SVN_RA_SVN_PORT);
687 SVN_ERR(make_connection(uri->hostname,
688 uri->port ? uri->port : SVN_RA_SVN_PORT,
690 conn = svn_ra_svn_create_conn4(sock, NULL, NULL,
691 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
695 /* Build the useragent string, querying the client for any
696 customizations it wishes to note. For historical reasons, we
697 still deliver the hard-coded client version info
698 (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
699 separately in the protocol/capabilities handshake below. But the
700 commit logic wants the combined form for use with the
701 SVN_PROP_TXN_USER_AGENT ephemeral property because that's
702 consistent with our DAV approach. */
703 if (sess->callbacks->get_client_string != NULL)
704 SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
705 &client_string, pool));
707 sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
708 client_string, SVN_VA_NULL);
710 sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
712 /* Make sure we set conn->session before reading from it,
713 * because the reader and writer functions expect a non-NULL value. */
715 conn->session = sess;
717 /* Read server's greeting. */
718 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
719 &mechlist, &server_caplist));
721 /* We support protocol version 2. */
723 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
724 _("Server requires minimum version %d"),
727 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
728 _("Server only supports versions up to %d"),
730 SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
732 /* All released versions of Subversion support edit-pipeline,
733 * so we do not support servers that do not. */
734 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
735 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
736 _("Server does not support edit pipelining"));
738 /* In protocol version 2, we send back our protocol version, our
739 * capability list, and the URL, and subsequently there is an auth
741 /* Client-side capabilities list: */
742 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
744 SVN_RA_SVN_CAP_EDIT_PIPELINE,
745 SVN_RA_SVN_CAP_SVNDIFF1,
746 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
747 SVN_RA_SVN_CAP_DEPTH,
748 SVN_RA_SVN_CAP_MERGEINFO,
749 SVN_RA_SVN_CAP_LOG_REVPROPS,
751 SVN_RA_SVN__DEFAULT_USERAGENT,
753 SVN_ERR(handle_auth_request(sess, pool));
755 /* This is where the security layer would go into effect if we
756 * supported security layers, which is a ways off. */
758 /* Read the repository's uuid and root URL, and perhaps learn more
759 capabilities that weren't available before now. */
760 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
761 &conn->repos_root, &repos_caplist));
763 SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
765 if (conn->repos_root)
767 conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
768 /* We should check that the returned string is a prefix of url, since
769 that's the API guarantee, but this isn't true for 1.0 servers.
770 Checking the length prevents client crashes. */
771 if (strlen(conn->repos_root) > strlen(url))
772 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
773 _("Impossibly long repository root from "
784 #define RA_SVN_DESCRIPTION \
785 N_("Module for accessing a repository using the svn network protocol.\n" \
786 " - with Cyrus SASL authentication")
788 #define RA_SVN_DESCRIPTION \
789 N_("Module for accessing a repository using the svn network protocol.")
792 static const char *ra_svn_get_description(apr_pool_t *pool)
794 return _(RA_SVN_DESCRIPTION);
797 static const char * const *
798 ra_svn_get_schemes(apr_pool_t *pool)
800 static const char *schemes[] = { "svn", NULL };
806 /* A simple whitelist to ensure the following are valid:
812 * with an extra restriction that a leading '-' is invalid.
815 is_valid_hostinfo(const char *hostinfo)
817 const char *p = hostinfo;
824 if (!svn_ctype_isalnum(*p) && !strchr(":.-_[]@", *p))
833 static svn_error_t *ra_svn_open(svn_ra_session_t *session,
834 const char **corrected_url,
836 const svn_ra_callbacks2_t *callbacks,
837 void *callback_baton,
838 svn_auth_baton_t *auth_baton,
840 apr_pool_t *result_pool,
841 apr_pool_t *scratch_pool)
843 apr_pool_t *sess_pool = svn_pool_create(result_pool);
844 svn_ra_svn__session_baton_t *sess;
845 const char *tunnel, **tunnel_argv;
847 svn_config_t *cfg, *cfg_client;
849 /* We don't support server-prescribed redirections in ra-svn. */
851 *corrected_url = NULL;
853 SVN_ERR(parse_url(url, &uri, sess_pool));
855 parse_tunnel(url, &tunnel, result_pool);
857 /* Use the default tunnel implementation if we got a tunnel name,
858 but either do not have tunnel handler callbacks installed, or
859 the handlers don't like the tunnel name. */
861 && (!callbacks->open_tunnel_func
862 || (callbacks->check_tunnel_func && callbacks->open_tunnel_func
863 && !callbacks->check_tunnel_func(callbacks->tunnel_baton,
866 const char *decoded_hostinfo;
868 decoded_hostinfo = svn_path_uri_decode(uri.hostinfo, result_pool);
870 if (!is_valid_hostinfo(decoded_hostinfo))
871 return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Invalid host '%s'"),
874 SVN_ERR(find_tunnel_agent(tunnel, decoded_hostinfo, &tunnel_argv,
875 config, result_pool));
881 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
883 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
884 svn_auth_set_parameter(auth_baton,
885 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
886 svn_auth_set_parameter(auth_baton,
887 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
889 /* We open the session in a subpool so we can get rid of it if we
890 reparent with a server that doesn't support reparenting. */
891 SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config,
892 callbacks, callback_baton,
893 auth_baton, sess_pool, scratch_pool));
894 session->priv = sess;
899 static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session,
900 svn_ra_session_t *old_session,
901 const char *new_session_url,
902 apr_pool_t *result_pool,
903 apr_pool_t *scratch_pool)
905 svn_ra_svn__session_baton_t *old_sess = old_session->priv;
907 SVN_ERR(ra_svn_open(new_session, NULL, new_session_url,
908 old_sess->callbacks, old_sess->callbacks_baton,
909 old_sess->auth_baton, old_sess->config,
910 result_pool, scratch_pool));
915 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
919 svn_ra_svn__session_baton_t *sess = ra_session->priv;
920 svn_ra_svn_conn_t *conn = sess->conn;
922 apr_pool_t *sess_pool;
923 svn_ra_svn__session_baton_t *new_sess;
926 SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
927 err = handle_auth_request(sess, pool);
930 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
931 sess->url = apr_pstrdup(sess->pool, url);
934 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
937 /* Servers before 1.4 doesn't support this command; try to reconnect
939 svn_error_clear(err);
940 /* Create a new subpool of the RA session pool. */
941 sess_pool = svn_pool_create(ra_session->pool);
942 err = parse_url(url, &uri, sess_pool);
944 err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv,
945 sess->config, sess->callbacks, sess->callbacks_baton,
946 sess->auth_baton, sess_pool, sess_pool);
947 /* We destroy the new session pool on error, since it is allocated in
948 the main session pool. */
951 svn_pool_destroy(sess_pool);
955 /* We have a new connection, assign it and destroy the old. */
956 ra_session->priv = new_sess;
957 svn_pool_destroy(sess->pool);
962 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
963 const char **url, apr_pool_t *pool)
965 svn_ra_svn__session_baton_t *sess = session->priv;
966 *url = apr_pstrdup(pool, sess->url);
970 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
971 svn_revnum_t *rev, apr_pool_t *pool)
973 svn_ra_svn__session_baton_t *sess_baton = session->priv;
974 svn_ra_svn_conn_t *conn = sess_baton->conn;
976 SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
977 SVN_ERR(handle_auth_request(sess_baton, pool));
978 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
982 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
983 svn_revnum_t *rev, apr_time_t tm,
986 svn_ra_svn__session_baton_t *sess_baton = session->priv;
987 svn_ra_svn_conn_t *conn = sess_baton->conn;
989 SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
990 SVN_ERR(handle_auth_request(sess_baton, pool));
991 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
995 /* Forward declaration. */
996 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
998 const char *capability,
1001 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1003 const svn_string_t *const *old_value_p,
1004 const svn_string_t *value,
1007 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1008 svn_ra_svn_conn_t *conn = sess_baton->conn;
1009 svn_boolean_t dont_care;
1010 const svn_string_t *old_value;
1011 svn_boolean_t has_atomic_revprops;
1013 SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
1014 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
1019 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
1020 SVN_ERR_ASSERT(has_atomic_revprops);
1023 old_value = *old_value_p;
1031 if (has_atomic_revprops)
1032 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
1036 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
1039 SVN_ERR(handle_auth_request(sess_baton, pool));
1040 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1041 return SVN_NO_ERROR;
1044 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
1047 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1048 svn_ra_svn_conn_t *conn = sess_baton->conn;
1051 return SVN_NO_ERROR;
1054 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
1057 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1058 svn_ra_svn_conn_t *conn = sess_baton->conn;
1060 if (!conn->repos_root)
1061 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
1062 _("Server did not send repository root"));
1063 *url = conn->repos_root;
1064 return SVN_NO_ERROR;
1067 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
1068 apr_hash_t **props, apr_pool_t *pool)
1070 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1071 svn_ra_svn_conn_t *conn = sess_baton->conn;
1072 apr_array_header_t *proplist;
1074 SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
1075 SVN_ERR(handle_auth_request(sess_baton, pool));
1076 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
1077 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1078 return SVN_NO_ERROR;
1081 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1083 svn_string_t **value, apr_pool_t *pool)
1085 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1086 svn_ra_svn_conn_t *conn = sess_baton->conn;
1088 SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
1089 SVN_ERR(handle_auth_request(sess_baton, pool));
1090 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
1091 return SVN_NO_ERROR;
1094 static svn_error_t *ra_svn_end_commit(void *baton)
1096 ra_svn_commit_callback_baton_t *ccb = baton;
1097 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
1099 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
1100 SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
1102 &(commit_info->revision),
1103 &(commit_info->date),
1104 &(commit_info->author),
1105 &(commit_info->post_commit_err)));
1107 commit_info->repos_root = apr_pstrdup(ccb->pool,
1108 ccb->sess_baton->conn->repos_root);
1111 SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
1113 return SVN_NO_ERROR;
1116 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
1117 const svn_delta_editor_t **editor,
1119 apr_hash_t *revprop_table,
1120 svn_commit_callback2_t callback,
1121 void *callback_baton,
1122 apr_hash_t *lock_tokens,
1123 svn_boolean_t keep_locks,
1126 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1127 svn_ra_svn_conn_t *conn = sess_baton->conn;
1128 ra_svn_commit_callback_baton_t *ccb;
1129 apr_hash_index_t *hi;
1130 apr_pool_t *iterpool;
1131 const svn_string_t *log_msg = svn_hash_gets(revprop_table,
1132 SVN_PROP_REVISION_LOG);
1134 if (log_msg == NULL &&
1135 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1137 return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
1138 _("ra_svn does not support not specifying "
1139 "a log message with pre-1.5 servers; "
1140 "consider passing an empty one, or upgrading "
1143 else if (log_msg == NULL)
1144 /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument
1145 to the 'commit' protocol command is non-optional; on the server side,
1146 only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The
1147 "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
1148 will have a NULL log message (not just "", really NULL).
1150 svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
1151 present; this was elevated to a protocol promise in r1498550 (and
1152 later documented in this comment) in order to fix the segmentation
1153 fault bug described in the log message of r1498550.*/
1154 log_msg = svn_string_create("", pool);
1156 /* If we're sending revprops other than svn:log, make sure the server won't
1157 silently ignore them. */
1158 if (apr_hash_count(revprop_table) > 1 &&
1159 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1160 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1161 _("Server doesn't support setting arbitrary "
1162 "revision properties during commit"));
1164 /* If the server supports ephemeral txnprops, add the one that
1165 reports the client's version level string. */
1166 if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1167 svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1169 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1170 svn_string_create(SVN_VER_NUMBER, pool));
1171 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1172 svn_string_create(sess_baton->useragent, pool));
1175 /* Tell the server we're starting the commit.
1176 Send log message here for backwards compatibility with servers
1178 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1182 iterpool = svn_pool_create(pool);
1183 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1187 const char *path, *token;
1189 svn_pool_clear(iterpool);
1190 apr_hash_this(hi, &key, NULL, &val);
1193 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1195 svn_pool_destroy(iterpool);
1197 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1198 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1199 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1200 SVN_ERR(handle_auth_request(sess_baton, pool));
1201 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1203 /* Remember a few arguments for when the commit is over. */
1204 ccb = apr_palloc(pool, sizeof(*ccb));
1205 ccb->sess_baton = sess_baton;
1207 ccb->new_rev = NULL;
1208 ccb->callback = callback;
1209 ccb->callback_baton = callback_baton;
1211 /* Fetch an editor for the caller to drive. The editor will call
1212 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1213 * in the new_rev, committed_date, and committed_author values. */
1214 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1215 ra_svn_end_commit, ccb);
1216 return SVN_NO_ERROR;
1219 /* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1220 const char * repos relative paths and properties for those paths, storing
1221 the result as an array of svn_prop_inherited_item_t *items. */
1222 static svn_error_t *
1223 parse_iproplist(apr_array_header_t **inherited_props,
1224 const apr_array_header_t *iproplist,
1225 svn_ra_session_t *session,
1226 apr_pool_t *result_pool,
1227 apr_pool_t *scratch_pool)
1231 apr_pool_t *iterpool;
1233 if (iproplist == NULL)
1235 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1236 capability we shouldn't be asking for inherited props, but if we
1237 did and the server sent back nothing then we'll want to handle
1239 *inherited_props = NULL;
1240 return SVN_NO_ERROR;
1243 *inherited_props = apr_array_make(
1244 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1246 iterpool = svn_pool_create(scratch_pool);
1248 for (i = 0; i < iproplist->nelts; i++)
1250 apr_array_header_t *iprop_list;
1251 char *parent_rel_path;
1253 apr_hash_index_t *hi;
1254 svn_prop_inherited_item_t *new_iprop =
1255 apr_palloc(result_pool, sizeof(*new_iprop));
1256 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1258 if (elt->kind != SVN_RA_SVN_LIST)
1259 return svn_error_create(
1260 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1261 _("Inherited proplist element not a list"));
1263 svn_pool_clear(iterpool);
1265 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1266 &parent_rel_path, &iprop_list));
1267 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1268 new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path);
1269 new_iprop->prop_hash = svn_hash__make(result_pool);
1270 for (hi = apr_hash_first(iterpool, iprops);
1272 hi = apr_hash_next(hi))
1274 const char *name = apr_hash_this_key(hi);
1275 svn_string_t *value = apr_hash_this_val(hi);
1276 svn_hash_sets(new_iprop->prop_hash,
1277 apr_pstrdup(result_pool, name),
1278 svn_string_dup(value, result_pool));
1280 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1283 svn_pool_destroy(iterpool);
1284 return SVN_NO_ERROR;
1287 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1288 svn_revnum_t rev, svn_stream_t *stream,
1289 svn_revnum_t *fetched_rev,
1293 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1294 svn_ra_svn_conn_t *conn = sess_baton->conn;
1295 apr_array_header_t *proplist;
1296 const char *expected_digest;
1297 svn_checksum_t *expected_checksum = NULL;
1298 svn_checksum_ctx_t *checksum_ctx;
1299 apr_pool_t *iterpool;
1301 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1302 (props != NULL), (stream != NULL)));
1303 SVN_ERR(handle_auth_request(sess_baton, pool));
1304 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1311 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1313 /* We're done if the contents weren't wanted. */
1315 return SVN_NO_ERROR;
1317 if (expected_digest)
1319 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1320 expected_digest, pool));
1321 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1324 /* Read the file's contents. */
1325 iterpool = svn_pool_create(pool);
1328 svn_ra_svn_item_t *item;
1330 svn_pool_clear(iterpool);
1331 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1332 if (item->kind != SVN_RA_SVN_STRING)
1333 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1334 _("Non-string as part of file contents"));
1335 if (item->u.string->len == 0)
1338 if (expected_checksum)
1339 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1340 item->u.string->len));
1342 SVN_ERR(svn_stream_write(stream, item->u.string->data,
1343 &item->u.string->len));
1345 svn_pool_destroy(iterpool);
1347 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1349 if (expected_checksum)
1351 svn_checksum_t *checksum;
1353 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1354 if (!svn_checksum_match(checksum, expected_checksum))
1355 return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1356 _("Checksum mismatch for '%s'"),
1360 return SVN_NO_ERROR;
1363 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1364 apr_hash_t **dirents,
1365 svn_revnum_t *fetched_rev,
1369 apr_uint32_t dirent_fields,
1372 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1373 svn_ra_svn_conn_t *conn = sess_baton->conn;
1374 apr_array_header_t *proplist, *dirlist;
1377 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1378 rev, (props != NULL), (dirents != NULL)));
1379 if (dirent_fields & SVN_DIRENT_KIND)
1380 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1381 if (dirent_fields & SVN_DIRENT_SIZE)
1382 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1383 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1384 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1385 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1386 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1387 if (dirent_fields & SVN_DIRENT_TIME)
1388 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1389 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1390 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1392 /* Always send the, nominally optional, want-iprops as "false" to
1393 workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1394 to see "true" if it is omitted. */
1395 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1397 SVN_ERR(handle_auth_request(sess_baton, pool));
1398 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1404 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1406 /* We're done if dirents aren't wanted. */
1408 return SVN_NO_ERROR;
1410 /* Interpret the directory list. */
1411 *dirents = svn_hash__make(pool);
1412 for (i = 0; i < dirlist->nelts; i++)
1414 const char *name, *kind, *cdate, *cauthor;
1415 svn_boolean_t has_props;
1416 svn_dirent_t *dirent;
1419 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1421 if (elt->kind != SVN_RA_SVN_LIST)
1422 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1423 _("Dirlist element not a list"));
1424 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1425 &name, &kind, &size, &has_props,
1426 &crev, &cdate, &cauthor));
1428 /* Nothing to sanitize here. Any multi-segment path is simply
1429 illegal in the hash returned by svn_ra_get_dir2. */
1430 if (strchr(name, '/'))
1431 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1432 _("Invalid directory entry name '%s'"),
1435 dirent = svn_dirent_create(pool);
1436 dirent->kind = svn_node_kind_from_word(kind);
1437 dirent->size = size;/* FIXME: svn_filesize_t */
1438 dirent->has_props = has_props;
1439 dirent->created_rev = crev;
1440 /* NOTE: the tuple's format string says CDATE may be NULL. But this
1441 function does not allow that. The server has always sent us some
1442 random date, however, so this just happens to work. But let's
1443 be wary of servers that are (improperly) fixed to send NULL.
1445 Note: they should NOT be "fixed" to send NULL, as that would break
1446 any older clients which received that NULL. But we may as well
1447 be defensive against a malicous server. */
1451 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1452 dirent->last_author = cauthor;
1453 svn_hash_sets(*dirents, name, dirent);
1456 return SVN_NO_ERROR;
1459 /* Converts a apr_uint64_t with values TRUE, FALSE or
1460 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1461 to a svn_tristate_t */
1462 static svn_tristate_t
1463 optbool_to_tristate(apr_uint64_t v)
1465 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
1466 return svn_tristate_true;
1468 return svn_tristate_false;
1470 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1473 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1474 server, which defaults to youngest. */
1475 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1476 svn_mergeinfo_catalog_t *catalog,
1477 const apr_array_header_t *paths,
1478 svn_revnum_t revision,
1479 svn_mergeinfo_inheritance_t inherit,
1480 svn_boolean_t include_descendants,
1483 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1484 svn_ra_svn_conn_t *conn = sess_baton->conn;
1486 apr_array_header_t *mergeinfo_tuple;
1487 svn_ra_svn_item_t *elt;
1490 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1491 for (i = 0; i < paths->nelts; i++)
1493 path = APR_ARRAY_IDX(paths, i, const char *);
1494 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1496 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1497 svn_inheritance_to_word(inherit),
1498 include_descendants));
1500 SVN_ERR(handle_auth_request(sess_baton, pool));
1501 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1504 if (mergeinfo_tuple->nelts > 0)
1506 *catalog = svn_hash__make(pool);
1507 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1509 svn_mergeinfo_t for_path;
1510 const char *to_parse;
1512 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1513 if (elt->kind != SVN_RA_SVN_LIST)
1514 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1515 _("Mergeinfo element is not a list"));
1516 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1518 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1519 /* Correct for naughty servers that send "relative" paths
1520 with leading slashes! */
1521 svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1525 return SVN_NO_ERROR;
1528 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1529 const svn_ra_reporter3_t **reporter,
1530 void **report_baton, svn_revnum_t rev,
1531 const char *target, svn_depth_t depth,
1532 svn_boolean_t send_copyfrom_args,
1533 svn_boolean_t ignore_ancestry,
1534 const svn_delta_editor_t *update_editor,
1537 apr_pool_t *scratch_pool)
1539 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1540 svn_ra_svn_conn_t *conn = sess_baton->conn;
1541 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1543 /* Tell the server we want to start an update. */
1544 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1545 depth, send_copyfrom_args,
1547 SVN_ERR(handle_auth_request(sess_baton, pool));
1549 /* Fetch a reporter for the caller to drive. The reporter will drive
1550 * update_editor upon finish_report(). */
1551 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1552 target, depth, reporter, report_baton));
1553 return SVN_NO_ERROR;
1556 static svn_error_t *
1557 ra_svn_switch(svn_ra_session_t *session,
1558 const svn_ra_reporter3_t **reporter,
1559 void **report_baton, svn_revnum_t rev,
1560 const char *target, svn_depth_t depth,
1561 const char *switch_url,
1562 svn_boolean_t send_copyfrom_args,
1563 svn_boolean_t ignore_ancestry,
1564 const svn_delta_editor_t *update_editor,
1566 apr_pool_t *result_pool,
1567 apr_pool_t *scratch_pool)
1569 apr_pool_t *pool = result_pool;
1570 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1571 svn_ra_svn_conn_t *conn = sess_baton->conn;
1572 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1574 /* Tell the server we want to start a switch. */
1575 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1577 send_copyfrom_args, ignore_ancestry));
1578 SVN_ERR(handle_auth_request(sess_baton, pool));
1580 /* Fetch a reporter for the caller to drive. The reporter will drive
1581 * update_editor upon finish_report(). */
1582 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1583 target, depth, reporter, report_baton));
1584 return SVN_NO_ERROR;
1587 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1588 const svn_ra_reporter3_t **reporter,
1589 void **report_baton,
1590 const char *target, svn_revnum_t rev,
1592 const svn_delta_editor_t *status_editor,
1593 void *status_baton, apr_pool_t *pool)
1595 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1596 svn_ra_svn_conn_t *conn = sess_baton->conn;
1597 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1599 /* Tell the server we want to start a status operation. */
1600 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1602 SVN_ERR(handle_auth_request(sess_baton, pool));
1604 /* Fetch a reporter for the caller to drive. The reporter will drive
1605 * status_editor upon finish_report(). */
1606 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1607 target, depth, reporter, report_baton));
1608 return SVN_NO_ERROR;
1611 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1612 const svn_ra_reporter3_t **reporter,
1613 void **report_baton,
1614 svn_revnum_t rev, const char *target,
1616 svn_boolean_t ignore_ancestry,
1617 svn_boolean_t text_deltas,
1618 const char *versus_url,
1619 const svn_delta_editor_t *diff_editor,
1620 void *diff_baton, apr_pool_t *pool)
1622 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1623 svn_ra_svn_conn_t *conn = sess_baton->conn;
1624 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1626 /* Tell the server we want to start a diff. */
1627 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1628 ignore_ancestry, versus_url,
1629 text_deltas, depth));
1630 SVN_ERR(handle_auth_request(sess_baton, pool));
1632 /* Fetch a reporter for the caller to drive. The reporter will drive
1633 * diff_editor upon finish_report(). */
1634 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1635 target, depth, reporter, report_baton));
1636 return SVN_NO_ERROR;
1640 static svn_error_t *
1641 perform_ra_svn_log(svn_error_t **outer_error,
1642 svn_ra_session_t *session,
1643 const apr_array_header_t *paths,
1644 svn_revnum_t start, svn_revnum_t end,
1646 svn_boolean_t discover_changed_paths,
1647 svn_boolean_t strict_node_history,
1648 svn_boolean_t include_merged_revisions,
1649 const apr_array_header_t *revprops,
1650 svn_log_entry_receiver_t receiver,
1651 void *receiver_baton,
1654 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1655 svn_ra_svn_conn_t *conn = sess_baton->conn;
1656 apr_pool_t *iterpool;
1661 svn_boolean_t want_custom_revprops;
1662 svn_boolean_t want_author = FALSE;
1663 svn_boolean_t want_message = FALSE;
1664 svn_boolean_t want_date = FALSE;
1667 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1670 for (i = 0; i < paths->nelts; i++)
1672 path = APR_ARRAY_IDX(paths, i, const char *);
1673 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1676 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1677 discover_changed_paths, strict_node_history,
1678 (apr_uint64_t) limit,
1679 include_merged_revisions));
1682 want_custom_revprops = FALSE;
1683 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1684 for (i = 0; i < revprops->nelts; i++)
1686 name = APR_ARRAY_IDX(revprops, i, char *);
1687 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1689 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1691 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1693 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1694 want_message = TRUE;
1696 want_custom_revprops = TRUE;
1698 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1702 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1706 want_message = TRUE;
1707 want_custom_revprops = TRUE;
1710 SVN_ERR(handle_auth_request(sess_baton, pool));
1712 /* Read the log messages. */
1713 iterpool = svn_pool_create(pool);
1716 apr_uint64_t has_children_param, invalid_revnum_param;
1717 apr_uint64_t has_subtractive_merge_param;
1718 svn_string_t *author, *date, *message;
1719 apr_array_header_t *cplist, *rplist;
1720 svn_log_entry_t *log_entry;
1721 svn_boolean_t has_children;
1722 svn_boolean_t subtractive_merge = FALSE;
1723 apr_uint64_t revprop_count;
1724 svn_ra_svn_item_t *item;
1728 svn_pool_clear(iterpool);
1729 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1730 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1732 if (item->kind != SVN_RA_SVN_LIST)
1733 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1734 _("Log entry not a list"));
1735 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1736 "lr(?s)(?s)(?s)?BBnl?B",
1737 &cplist, &rev, &author, &date,
1738 &message, &has_children_param,
1739 &invalid_revnum_param,
1740 &revprop_count, &rplist,
1741 &has_subtractive_merge_param));
1742 if (want_custom_revprops && rplist == NULL)
1744 /* Caller asked for custom revprops, but server is too old. */
1745 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1746 _("Server does not support custom revprops"
1750 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1751 has_children = FALSE;
1753 has_children = (svn_boolean_t) has_children_param;
1755 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1756 subtractive_merge = FALSE;
1758 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1760 /* Because the svn protocol won't let us send an invalid revnum, we have
1761 to recover that fact using the extra parameter. */
1762 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1763 && invalid_revnum_param)
1764 rev = SVN_INVALID_REVNUM;
1766 if (cplist->nelts > 0)
1768 /* Interpret the changed-paths list. */
1769 cphash = svn_hash__make(iterpool);
1770 for (i = 0; i < cplist->nelts; i++)
1772 svn_log_changed_path2_t *change;
1773 svn_string_t *cpath;
1774 const char *copy_path, *action, *kind_str;
1775 apr_uint64_t text_mods, prop_mods;
1776 svn_revnum_t copy_rev;
1777 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1780 if (elt->kind != SVN_RA_SVN_LIST)
1781 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1782 _("Changed-path entry not a list"));
1783 SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1784 &cpath, &action, ©_path,
1785 ©_rev, &kind_str,
1786 &text_mods, &prop_mods));
1788 if (!svn_fspath__is_canonical(cpath->data))
1790 cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1791 cpath->len = strlen(cpath->data);
1793 if (copy_path && !svn_fspath__is_canonical(copy_path))
1794 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1796 change = svn_log_changed_path2_create(iterpool);
1797 change->action = *action;
1798 change->copyfrom_path = copy_path;
1799 change->copyfrom_rev = copy_rev;
1800 change->node_kind = svn_node_kind_from_word(kind_str);
1801 change->text_modified = optbool_to_tristate(text_mods);
1802 change->props_modified = optbool_to_tristate(prop_mods);
1803 apr_hash_set(cphash, cpath->data, cpath->len, change);
1810 - Except if the server sends more than a >= 1 limit top level items
1811 - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1812 in an earlier invocation. */
1813 if (! (limit && (nest_level == 0) && (++nreceived > limit))
1817 log_entry = svn_log_entry_create(iterpool);
1819 log_entry->changed_paths = cphash;
1820 log_entry->changed_paths2 = cphash;
1821 log_entry->revision = rev;
1822 log_entry->has_children = has_children;
1823 log_entry->subtractive_merge = subtractive_merge;
1825 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1826 &log_entry->revprops));
1827 if (log_entry->revprops == NULL)
1828 log_entry->revprops = svn_hash__make(iterpool);
1830 if (author && want_author)
1831 svn_hash_sets(log_entry->revprops,
1832 SVN_PROP_REVISION_AUTHOR, author);
1833 if (date && want_date)
1834 svn_hash_sets(log_entry->revprops,
1835 SVN_PROP_REVISION_DATE, date);
1836 if (message && want_message)
1837 svn_hash_sets(log_entry->revprops,
1838 SVN_PROP_REVISION_LOG, message);
1840 err = receiver(receiver_baton, log_entry, iterpool);
1841 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1843 *outer_error = svn_error_trace(
1844 svn_error_compose_create(*outer_error, err));
1849 if (log_entry->has_children)
1853 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1855 SVN_ERR_ASSERT(nest_level);
1860 svn_pool_destroy(iterpool);
1862 /* Read the response. */
1863 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1866 static svn_error_t *
1867 ra_svn_log(svn_ra_session_t *session,
1868 const apr_array_header_t *paths,
1869 svn_revnum_t start, svn_revnum_t end,
1871 svn_boolean_t discover_changed_paths,
1872 svn_boolean_t strict_node_history,
1873 svn_boolean_t include_merged_revisions,
1874 const apr_array_header_t *revprops,
1875 svn_log_entry_receiver_t receiver,
1876 void *receiver_baton, apr_pool_t *pool)
1878 svn_error_t *outer_error = NULL;
1881 err = svn_error_trace(perform_ra_svn_log(&outer_error,
1885 discover_changed_paths,
1886 strict_node_history,
1887 include_merged_revisions,
1889 receiver, receiver_baton,
1891 return svn_error_trace(
1892 svn_error_compose_create(outer_error,
1898 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1899 const char *path, svn_revnum_t rev,
1900 svn_node_kind_t *kind, apr_pool_t *pool)
1902 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1903 svn_ra_svn_conn_t *conn = sess_baton->conn;
1904 const char *kind_word;
1906 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1907 SVN_ERR(handle_auth_request(sess_baton, pool));
1908 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1909 *kind = svn_node_kind_from_word(kind_word);
1910 return SVN_NO_ERROR;
1914 /* If ERR is a command not supported error, wrap it in a
1915 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1916 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1919 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1920 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1926 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1927 const char *path, svn_revnum_t rev,
1928 svn_dirent_t **dirent, apr_pool_t *pool)
1930 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1931 svn_ra_svn_conn_t *conn = sess_baton->conn;
1932 apr_array_header_t *list = NULL;
1933 svn_dirent_t *the_dirent;
1935 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1936 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1937 N_("'stat' not implemented")));
1938 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1946 const char *kind, *cdate, *cauthor;
1947 svn_boolean_t has_props;
1951 SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1952 &kind, &size, &has_props,
1953 &crev, &cdate, &cauthor));
1955 the_dirent = svn_dirent_create(pool);
1956 the_dirent->kind = svn_node_kind_from_word(kind);
1957 the_dirent->size = size;/* FIXME: svn_filesize_t */
1958 the_dirent->has_props = has_props;
1959 the_dirent->created_rev = crev;
1960 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1961 the_dirent->last_author = cauthor;
1963 *dirent = the_dirent;
1966 return SVN_NO_ERROR;
1970 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1971 apr_hash_t **locations,
1973 svn_revnum_t peg_revision,
1974 const apr_array_header_t *location_revisions,
1977 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1978 svn_ra_svn_conn_t *conn = sess_baton->conn;
1979 svn_revnum_t revision;
1980 svn_boolean_t is_done;
1983 /* Transmit the parameters. */
1984 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1985 "get-locations", path, peg_revision));
1986 for (i = 0; i < location_revisions->nelts; i++)
1988 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1989 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1992 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1994 /* Servers before 1.1 don't support this command. Check for this here. */
1995 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1996 N_("'get-locations' not implemented")));
1998 /* Read the hash items. */
2000 *locations = apr_hash_make(pool);
2003 svn_ra_svn_item_t *item;
2004 const char *ret_path;
2006 SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
2007 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2009 else if (item->kind != SVN_RA_SVN_LIST)
2010 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2011 _("Location entry not a list"));
2014 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
2015 &revision, &ret_path));
2016 ret_path = svn_fspath__canonicalize(ret_path, pool);
2017 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
2019 sizeof(revision), ret_path);
2023 /* Read the response. This is so the server would have a chance to
2024 * report an error. */
2025 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
2028 static svn_error_t *
2029 ra_svn_get_location_segments(svn_ra_session_t *session,
2031 svn_revnum_t peg_revision,
2032 svn_revnum_t start_rev,
2033 svn_revnum_t end_rev,
2034 svn_location_segment_receiver_t receiver,
2035 void *receiver_baton,
2038 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2039 svn_ra_svn_conn_t *conn = sess_baton->conn;
2040 svn_boolean_t is_done;
2041 apr_pool_t *iterpool = svn_pool_create(pool);
2043 /* Transmit the parameters. */
2044 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2045 "get-location-segments",
2046 path, peg_revision, start_rev, end_rev));
2048 /* Servers before 1.5 don't support this command. Check for this here. */
2049 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2050 N_("'get-location-segments'"
2051 " not implemented")));
2053 /* Parse the response. */
2057 svn_revnum_t range_start, range_end;
2058 svn_ra_svn_item_t *item;
2059 const char *ret_path;
2061 svn_pool_clear(iterpool);
2062 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2063 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2065 else if (item->kind != SVN_RA_SVN_LIST)
2066 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2067 _("Location segment entry not a list"));
2070 svn_location_segment_t *segment = apr_pcalloc(iterpool,
2072 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2073 &range_start, &range_end, &ret_path));
2074 if (! (SVN_IS_VALID_REVNUM(range_start)
2075 && SVN_IS_VALID_REVNUM(range_end)))
2076 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2077 _("Expected valid revision range"));
2079 ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2080 segment->path = ret_path;
2081 segment->range_start = range_start;
2082 segment->range_end = range_end;
2083 SVN_ERR(receiver(segment, receiver_baton, iterpool));
2086 svn_pool_destroy(iterpool);
2088 /* Read the response. This is so the server would have a chance to
2089 * report an error. */
2090 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2092 return SVN_NO_ERROR;
2095 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2097 svn_revnum_t start, svn_revnum_t end,
2098 svn_boolean_t include_merged_revisions,
2099 svn_file_rev_handler_t handler,
2100 void *handler_baton, apr_pool_t *pool)
2102 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2103 apr_pool_t *rev_pool, *chunk_pool;
2104 svn_boolean_t has_txdelta;
2105 svn_boolean_t had_revision = FALSE;
2107 /* One sub-pool for each revision and one for each txdelta chunk.
2108 Note that the rev_pool must live during the following txdelta. */
2109 rev_pool = svn_pool_create(pool);
2110 chunk_pool = svn_pool_create(pool);
2112 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2114 include_merged_revisions));
2116 /* Servers before 1.1 don't support this command. Check for this here. */
2117 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2118 N_("'get-file-revs' not implemented")));
2122 apr_array_header_t *rev_proplist, *proplist;
2123 apr_uint64_t merged_rev_param;
2124 apr_array_header_t *props;
2125 svn_ra_svn_item_t *item;
2126 apr_hash_t *rev_props;
2129 svn_boolean_t merged_rev;
2130 svn_txdelta_window_handler_t d_handler;
2133 svn_pool_clear(rev_pool);
2134 svn_pool_clear(chunk_pool);
2135 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2136 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2138 /* Either we've got a correct revision or we will error out below. */
2139 had_revision = TRUE;
2140 if (item->kind != SVN_RA_SVN_LIST)
2141 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2142 _("Revision entry not a list"));
2144 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2145 "crll?B", &p, &rev, &rev_proplist,
2146 &proplist, &merged_rev_param));
2147 p = svn_fspath__canonicalize(p, rev_pool);
2148 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2149 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2150 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2153 merged_rev = (svn_boolean_t) merged_rev_param;
2155 /* Get the first delta chunk so we know if there is a delta. */
2156 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2157 if (item->kind != SVN_RA_SVN_STRING)
2158 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2159 _("Text delta chunk not a string"));
2160 has_txdelta = item->u.string->len > 0;
2162 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2163 has_txdelta ? &d_handler : NULL, &d_baton,
2166 /* Process the text delta if any. */
2169 svn_stream_t *stream;
2171 if (d_handler && d_handler != svn_delta_noop_window_handler)
2172 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2176 while (item->u.string->len > 0)
2180 size = item->u.string->len;
2182 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2183 svn_pool_clear(chunk_pool);
2185 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2187 if (item->kind != SVN_RA_SVN_STRING)
2188 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2189 _("Text delta chunk not a string"));
2192 SVN_ERR(svn_stream_close(stream));
2196 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2198 /* Return error if we didn't get any revisions. */
2200 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2201 _("The get-file-revs command didn't return "
2204 svn_pool_destroy(chunk_pool);
2205 svn_pool_destroy(rev_pool);
2207 return SVN_NO_ERROR;
2210 /* For each path in PATH_REVS, send a 'lock' command to the server.
2211 Used with 1.2.x series servers which support locking, but of only
2212 one path at a time. ra_svn_lock(), which supports 'lock-many'
2213 is now the default. See svn_ra_lock() docstring for interface details. */
2214 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2215 apr_hash_t *path_revs,
2216 const char *comment,
2217 svn_boolean_t steal_lock,
2218 svn_ra_lock_callback_t lock_func,
2222 svn_ra_svn__session_baton_t *sess = session->priv;
2223 svn_ra_svn_conn_t* conn = sess->conn;
2224 apr_array_header_t *list;
2225 apr_hash_index_t *hi;
2226 apr_pool_t *iterpool = svn_pool_create(pool);
2228 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2234 svn_revnum_t *revnum;
2235 svn_error_t *err, *callback_err = NULL;
2237 svn_pool_clear(iterpool);
2239 apr_hash_this(hi, &key, NULL, &val);
2243 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2244 steal_lock, *revnum));
2246 /* Servers before 1.2 doesn't support locking. Check this here. */
2247 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2248 N_("Server doesn't support "
2249 "the lock command")));
2251 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2254 SVN_ERR(parse_lock(list, iterpool, &lock));
2256 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2260 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2263 svn_error_clear(err);
2266 return callback_err;
2269 svn_pool_destroy(iterpool);
2271 return SVN_NO_ERROR;
2274 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2275 Used with 1.2.x series servers which support unlocking, but of only
2276 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
2277 now the default. See svn_ra_unlock() docstring for interface details. */
2278 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2279 apr_hash_t *path_tokens,
2280 svn_boolean_t break_lock,
2281 svn_ra_lock_callback_t lock_func,
2285 svn_ra_svn__session_baton_t *sess = session->priv;
2286 svn_ra_svn_conn_t* conn = sess->conn;
2287 apr_hash_index_t *hi;
2288 apr_pool_t *iterpool = svn_pool_create(pool);
2290 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2296 svn_error_t *err, *callback_err = NULL;
2298 svn_pool_clear(iterpool);
2300 apr_hash_this(hi, &key, NULL, &val);
2302 if (strcmp(val, "") != 0)
2307 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2310 /* Servers before 1.2 don't support locking. Check this here. */
2311 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2312 N_("Server doesn't support the unlock "
2315 err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2317 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2321 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2323 svn_error_clear(err);
2326 return callback_err;
2329 svn_pool_destroy(iterpool);
2331 return SVN_NO_ERROR;
2334 /* Tell the server to lock all paths in PATH_REVS.
2335 See svn_ra_lock() for interface details. */
2336 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2337 apr_hash_t *path_revs,
2338 const char *comment,
2339 svn_boolean_t steal_lock,
2340 svn_ra_lock_callback_t lock_func,
2344 svn_ra_svn__session_baton_t *sess = session->priv;
2345 svn_ra_svn_conn_t *conn = sess->conn;
2346 apr_hash_index_t *hi;
2348 apr_pool_t *iterpool = svn_pool_create(pool);
2350 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2351 comment, steal_lock));
2353 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2358 svn_revnum_t *revnum;
2360 svn_pool_clear(iterpool);
2361 apr_hash_this(hi, &key, NULL, &val);
2365 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2368 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2370 err = handle_auth_request(sess, pool);
2372 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2374 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2376 svn_error_clear(err);
2377 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2378 lock_func, lock_baton, pool);
2384 /* Loop over responses to get lock information. */
2385 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2387 svn_ra_svn_item_t *elt;
2390 svn_error_t *callback_err;
2393 apr_array_header_t *list;
2395 apr_hash_this(hi, &key, NULL, NULL);
2398 svn_pool_clear(iterpool);
2399 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2401 /* The server might have encountered some sort of fatal error in
2402 the middle of the request list. If this happens, it will
2403 transmit "done" to end the lock-info early, and then the
2404 overall command response will talk about the fatal error. */
2405 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2408 if (elt->kind != SVN_RA_SVN_LIST)
2409 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2410 _("Lock response not a list"));
2412 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2415 if (strcmp(status, "failure") == 0)
2416 err = svn_ra_svn__handle_failure_status(list, iterpool);
2417 else if (strcmp(status, "success") == 0)
2419 SVN_ERR(parse_lock(list, iterpool, &lock));
2423 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2424 _("Unknown status for lock command"));
2427 callback_err = lock_func(lock_baton, path, TRUE,
2431 callback_err = SVN_NO_ERROR;
2433 svn_error_clear(err);
2436 return callback_err;
2439 /* If we didn't break early above, and the whole hash was traversed,
2440 read the final "done" from the server. */
2443 svn_ra_svn_item_t *elt;
2445 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2446 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2447 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2448 _("Didn't receive end marker for lock "
2452 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2454 svn_pool_destroy(iterpool);
2456 return SVN_NO_ERROR;
2459 /* Tell the server to unlock all paths in PATH_TOKENS.
2460 See svn_ra_unlock() for interface details. */
2461 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2462 apr_hash_t *path_tokens,
2463 svn_boolean_t break_lock,
2464 svn_ra_lock_callback_t lock_func,
2468 svn_ra_svn__session_baton_t *sess = session->priv;
2469 svn_ra_svn_conn_t *conn = sess->conn;
2470 apr_hash_index_t *hi;
2471 apr_pool_t *iterpool = svn_pool_create(pool);
2475 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2478 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2484 svn_pool_clear(iterpool);
2485 apr_hash_this(hi, &key, NULL, &val);
2488 if (strcmp(val, "") != 0)
2493 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2496 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2498 err = handle_auth_request(sess, pool);
2500 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2503 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2505 svn_error_clear(err);
2506 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2513 /* Loop over responses to unlock files. */
2514 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2516 svn_ra_svn_item_t *elt;
2518 svn_error_t *callback_err;
2520 apr_array_header_t *list;
2522 svn_pool_clear(iterpool);
2524 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2526 /* The server might have encountered some sort of fatal error in
2527 the middle of the request list. If this happens, it will
2528 transmit "done" to end the lock-info early, and then the
2529 overall command response will talk about the fatal error. */
2530 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2533 apr_hash_this(hi, &key, NULL, NULL);
2536 if (elt->kind != SVN_RA_SVN_LIST)
2537 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2538 _("Unlock response not a list"));
2540 SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2543 if (strcmp(status, "failure") == 0)
2544 err = svn_ra_svn__handle_failure_status(list, iterpool);
2545 else if (strcmp(status, "success") == 0)
2547 SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2551 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2552 _("Unknown status for unlock command"));
2555 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2558 callback_err = SVN_NO_ERROR;
2560 svn_error_clear(err);
2563 return callback_err;
2566 /* If we didn't break early above, and the whole hash was traversed,
2567 read the final "done" from the server. */
2570 svn_ra_svn_item_t *elt;
2572 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2573 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2574 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2575 _("Didn't receive end marker for unlock "
2579 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2581 svn_pool_destroy(iterpool);
2583 return SVN_NO_ERROR;
2586 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2591 svn_ra_svn__session_baton_t *sess = session->priv;
2592 svn_ra_svn_conn_t* conn = sess->conn;
2593 apr_array_header_t *list;
2595 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2597 /* Servers before 1.2 doesn't support locking. Check this here. */
2598 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2599 N_("Server doesn't support the get-lock "
2602 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2604 SVN_ERR(parse_lock(list, pool, lock));
2608 return SVN_NO_ERROR;
2611 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2612 to prevent a dependency cycle. */
2613 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2614 const char **rel_path,
2618 const char *root_url;
2620 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2621 *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2623 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2624 _("'%s' isn't a child of repository root "
2627 return SVN_NO_ERROR;
2630 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2636 svn_ra_svn__session_baton_t *sess = session->priv;
2637 svn_ra_svn_conn_t* conn = sess->conn;
2638 apr_array_header_t *list;
2639 const char *full_url, *abs_path;
2642 /* Figure out the repository abspath from PATH. */
2643 full_url = svn_path_url_add_component2(sess->url, path, pool);
2644 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2645 abs_path = svn_fspath__canonicalize(abs_path, pool);
2647 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2649 /* Servers before 1.2 doesn't support locking. Check this here. */
2650 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2651 N_("Server doesn't support the get-lock "
2654 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2656 *locks = apr_hash_make(pool);
2658 for (i = 0; i < list->nelts; ++i)
2661 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2663 if (elt->kind != SVN_RA_SVN_LIST)
2664 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2665 _("Lock element not a list"));
2666 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2668 /* Filter out unwanted paths. Since Subversion only allows
2669 locks on files, we can treat depth=immediates the same as
2670 depth=files for filtering purposes. Meaning, we'll keep
2673 a) its path is the very path we queried, or
2674 b) we've asked for a fully recursive answer, or
2675 c) we've asked for depth=files or depth=immediates, and this
2676 lock is on an immediate child of our query path.
2678 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2680 svn_hash_sets(*locks, lock->path, lock);
2682 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2684 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2685 if (relpath && (svn_path_component_count(relpath) == 1))
2686 svn_hash_sets(*locks, lock->path, lock);
2690 return SVN_NO_ERROR;
2694 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2695 svn_revnum_t revision,
2696 svn_revnum_t low_water_mark,
2697 svn_boolean_t send_deltas,
2698 const svn_delta_editor_t *editor,
2702 svn_ra_svn__session_baton_t *sess = session->priv;
2704 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2705 low_water_mark, send_deltas));
2707 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2708 N_("Server doesn't support the replay "
2711 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2714 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2718 static svn_error_t *
2719 ra_svn_replay_range(svn_ra_session_t *session,
2720 svn_revnum_t start_revision,
2721 svn_revnum_t end_revision,
2722 svn_revnum_t low_water_mark,
2723 svn_boolean_t send_deltas,
2724 svn_ra_replay_revstart_callback_t revstart_func,
2725 svn_ra_replay_revfinish_callback_t revfinish_func,
2729 svn_ra_svn__session_baton_t *sess = session->priv;
2730 apr_pool_t *iterpool;
2732 svn_boolean_t drive_aborted = FALSE;
2734 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2735 start_revision, end_revision,
2736 low_water_mark, send_deltas));
2738 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2739 N_("Server doesn't support the "
2740 "replay-range command")));
2742 iterpool = svn_pool_create(pool);
2743 for (rev = start_revision; rev <= end_revision; rev++)
2745 const svn_delta_editor_t *editor;
2747 apr_hash_t *rev_props;
2749 apr_array_header_t *list;
2751 svn_pool_clear(iterpool);
2753 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2754 "wl", &word, &list));
2755 if (strcmp(word, "revprops") != 0)
2756 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2757 _("Expected 'revprops', found '%s'"),
2760 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2762 SVN_ERR(revstart_func(rev, replay_baton,
2763 &editor, &edit_baton,
2766 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2768 &drive_aborted, TRUE));
2769 /* If drive_editor2() aborted the commit, do NOT try to call
2770 revfinish_func and commit the transaction! */
2771 if (drive_aborted) {
2772 svn_pool_destroy(iterpool);
2773 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2774 _("Error while replaying commit"));
2776 SVN_ERR(revfinish_func(rev, replay_baton,
2781 svn_pool_destroy(iterpool);
2783 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2787 static svn_error_t *
2788 ra_svn_has_capability(svn_ra_session_t *session,
2790 const char *capability,
2793 svn_ra_svn__session_baton_t *sess = session->priv;
2794 static const char* capabilities[][2] =
2796 /* { ra capability string, svn:// wire capability string} */
2797 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2798 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2799 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2800 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2801 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2802 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2803 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2804 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2805 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2806 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2807 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2809 {NULL, NULL} /* End of list marker */
2815 for (i = 0; capabilities[i][0]; i++)
2817 if (strcmp(capability, capabilities[i][0]) == 0)
2819 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2820 return SVN_NO_ERROR;
2824 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2825 _("Don't know anything about capability '%s'"),
2829 static svn_error_t *
2830 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2832 svn_revnum_t peg_revision,
2833 svn_revnum_t end_revision,
2834 svn_revnum_t *revision_deleted,
2838 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2839 svn_ra_svn_conn_t *conn = sess_baton->conn;
2841 /* Transmit the parameters. */
2842 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2843 peg_revision, end_revision));
2845 /* Servers before 1.6 don't support this command. Check for this here. */
2846 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2847 N_("'get-deleted-rev' not implemented")));
2849 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2853 static svn_error_t *
2854 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2855 svn_delta_shim_callbacks_t *callbacks)
2857 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2858 svn_ra_svn_conn_t *conn = sess_baton->conn;
2860 conn->shim_callbacks = callbacks;
2862 return SVN_NO_ERROR;
2865 static svn_error_t *
2866 ra_svn_get_inherited_props(svn_ra_session_t *session,
2867 apr_array_header_t **iprops,
2869 svn_revnum_t revision,
2870 apr_pool_t *result_pool,
2871 apr_pool_t *scratch_pool)
2873 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2874 svn_ra_svn_conn_t *conn = sess_baton->conn;
2875 apr_array_header_t *iproplist;
2876 svn_boolean_t iprop_capable;
2878 SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2879 SVN_RA_CAPABILITY_INHERITED_PROPS,
2882 /* If we don't support native iprop handling, use the implementation
2885 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2887 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2889 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2890 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2891 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2894 return SVN_NO_ERROR;
2897 static const svn_ra__vtable_t ra_svn_vtable = {
2899 ra_svn_get_description,
2904 ra_svn_get_session_url,
2905 ra_svn_get_latest_rev,
2906 ra_svn_get_dated_rev,
2907 ra_svn_change_rev_prop,
2908 ra_svn_rev_proplist,
2913 ra_svn_get_mergeinfo,
2922 ra_svn_get_repos_root,
2923 ra_svn_get_locations,
2924 ra_svn_get_location_segments,
2925 ra_svn_get_file_revs,
2931 ra_svn_has_capability,
2932 ra_svn_replay_range,
2933 ra_svn_get_deleted_rev,
2934 ra_svn_register_editor_shim_callbacks,
2935 ra_svn_get_inherited_props
2939 svn_ra_svn__init(const svn_version_t *loader_version,
2940 const svn_ra__vtable_t **vtable,
2943 static const svn_version_checklist_t checklist[] =
2945 { "svn_subr", svn_subr_version },
2946 { "svn_delta", svn_delta_version },
2950 SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2952 /* Simplified version check to make sure we can safely use the
2953 VTABLE parameter. The RA loader does a more exhaustive check. */
2954 if (loader_version->major != SVN_VER_MAJOR)
2956 return svn_error_createf
2957 (SVN_ERR_VERSION_MISMATCH, NULL,
2958 _("Unsupported RA loader version (%d) for ra_svn"),
2959 loader_version->major);
2962 *vtable = &ra_svn_vtable;
2964 #ifdef SVN_HAVE_SASL
2965 SVN_ERR(svn_ra_svn__sasl_init());
2968 return SVN_NO_ERROR;
2971 /* Compatibility wrapper for the 1.1 and before API. */
2972 #define NAME "ra_svn"
2973 #define DESCRIPTION RA_SVN_DESCRIPTION
2974 #define VTBL ra_svn_vtable
2975 #define INITFUNC svn_ra_svn__init
2976 #define COMPAT_INITFUNC svn_ra_svn_init
2977 #include "../libsvn_ra/wrapper_template.h"