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_string_private.h"
55 #include "private/svn_subr_private.h"
57 #include "../libsvn_ra/ra_loader.h"
62 #define DO_AUTH svn_ra_svn__do_cyrus_auth
64 #define DO_AUTH svn_ra_svn__do_internal_auth
67 /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
68 whatever reason) deems svn_depth_immediates as non-recursive, which
69 is ... kinda true, but not true enough for our purposes. We need
70 our requested recursion level to be *at least* as recursive as the
71 real depth we're looking for.
73 #define DEPTH_TO_RECURSE(d) \
74 ((d) == svn_depth_unknown || (d) > svn_depth_files)
76 typedef struct ra_svn_commit_callback_baton_t {
77 svn_ra_svn__session_baton_t *sess_baton;
79 svn_revnum_t *new_rev;
80 svn_commit_callback2_t callback;
82 } ra_svn_commit_callback_baton_t;
84 typedef struct ra_svn_reporter_baton_t {
85 svn_ra_svn__session_baton_t *sess_baton;
86 svn_ra_svn_conn_t *conn;
88 const svn_delta_editor_t *editor;
90 } ra_svn_reporter_baton_t;
92 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
94 static void parse_tunnel(const char *url, const char **tunnel,
99 if (strncasecmp(url, "svn", 3) != 0)
103 /* Get the tunnel specification, if any. */
109 p = strchr(url, ':');
112 *tunnel = apr_pstrmemdup(pool, url, p - url);
116 static svn_error_t *make_connection(const char *hostname, unsigned short port,
117 apr_socket_t **sock, apr_pool_t *pool)
121 int family = APR_INET;
123 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
124 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
125 create IPV6 sockets. */
128 #ifdef MAX_SECS_TO_LINGER
129 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
131 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
132 APR_PROTO_TCP, pool);
136 apr_socket_close(*sock);
141 /* Resolve the hostname. */
142 status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
144 return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
146 /* Iterate through the returned list of addresses attempting to
147 * connect to each in turn. */
150 /* Create the socket. */
151 #ifdef MAX_SECS_TO_LINGER
152 /* ### old APR interface */
153 status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
155 status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
158 if (status == APR_SUCCESS)
160 status = apr_socket_connect(*sock, sa);
161 if (status != APR_SUCCESS)
162 apr_socket_close(*sock);
166 while (status != APR_SUCCESS && sa);
169 return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
172 /* Enable TCP keep-alives on the socket so we time out when
173 * the connection breaks due to network-layer problems.
174 * If the peer has dropped the connection due to a network partition
175 * or a crash, or if the peer no longer considers the connection
176 * valid because we are behind a NAT and our public IP has changed,
177 * it will respond to the keep-alive probe with a RST instead of an
178 * acknowledgment segment, which will cause svn to abort the session
179 * even while it is currently blocked waiting for data from the peer.
180 * See issue #3347. */
181 status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
184 /* It's not a fatal error if we cannot enable keep-alives. */
190 /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
191 property diffs in LIST, received from the server. */
192 static svn_error_t *parse_prop_diffs(const svn_ra_svn__list_t *list,
194 apr_array_header_t **diffs)
198 *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
200 for (i = 0; i < list->nelts; i++)
203 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(list, i);
205 if (elt->kind != SVN_RA_SVN_LIST)
206 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
207 _("Prop diffs element not a list"));
208 prop = apr_array_push(*diffs);
209 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "c(?s)",
210 &prop->name, &prop->value));
215 /* Parse a lockdesc, provided in LIST as specified by the protocol into
216 LOCK, allocated in POOL. */
217 static svn_error_t *parse_lock(const svn_ra_svn__list_t *list,
221 const char *cdate, *edate;
222 *lock = svn_lock_create(pool);
223 SVN_ERR(svn_ra_svn__parse_tuple(list, "ccc(?c)c(?c)", &(*lock)->path,
224 &(*lock)->token, &(*lock)->owner,
225 &(*lock)->comment, &cdate, &edate));
226 (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
227 SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
229 SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
233 /* --- AUTHENTICATION ROUTINES --- */
235 svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
237 const char *mech, const char *mech_arg)
239 return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg));
242 static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
245 svn_ra_svn_conn_t *conn = sess->conn;
246 svn_ra_svn__list_t *mechlist;
249 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
250 if (mechlist->nelts == 0)
252 return DO_AUTH(sess, mechlist, realm, pool);
255 /* --- REPORTER IMPLEMENTATION --- */
257 static svn_error_t *ra_svn_set_path(void *baton, const char *path,
260 svn_boolean_t start_empty,
261 const char *lock_token,
264 ra_svn_reporter_baton_t *b = baton;
266 SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
267 start_empty, lock_token, depth));
271 static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
274 ra_svn_reporter_baton_t *b = baton;
276 SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
280 static svn_error_t *ra_svn_link_path(void *baton, const char *path,
284 svn_boolean_t start_empty,
285 const char *lock_token,
288 ra_svn_reporter_baton_t *b = baton;
290 SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
291 start_empty, lock_token, depth));
295 static svn_error_t *ra_svn_finish_report(void *baton,
298 ra_svn_reporter_baton_t *b = baton;
300 SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
301 SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
302 SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
304 SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
308 static svn_error_t *ra_svn_abort_report(void *baton,
311 ra_svn_reporter_baton_t *b = baton;
313 SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
317 static svn_ra_reporter3_t ra_svn_reporter = {
321 ra_svn_finish_report,
325 /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
326 * EDITOR/EDIT_BATON when it gets the finish_report() call.
328 * Allocate the new reporter in POOL.
331 ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
333 const svn_delta_editor_t *editor,
337 const svn_ra_reporter3_t **reporter,
340 ra_svn_reporter_baton_t *b;
341 const svn_delta_editor_t *filter_editor;
344 /* We can skip the depth filtering when the user requested
345 depth_files or depth_infinity because the server will
346 transmit the right stuff anyway. */
347 if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
348 && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
350 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
352 editor, edit_baton, depth,
355 editor = filter_editor;
356 edit_baton = filter_baton;
359 b = apr_palloc(pool, sizeof(*b));
360 b->sess_baton = sess_baton;
361 b->conn = sess_baton->conn;
364 b->edit_baton = edit_baton;
366 *reporter = &ra_svn_reporter;
372 /* --- RA LAYER IMPLEMENTATION --- */
374 /* (Note: *ARGV_P is an output parameter.) */
375 static svn_error_t *find_tunnel_agent(const char *tunnel,
376 const char *hostinfo,
377 const char ***argv_p,
378 apr_hash_t *config, apr_pool_t *pool)
381 const char *val, *var, *cmd;
388 /* Look up the tunnel specification in config. */
389 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
390 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
392 /* We have one predefined tunnel scheme, if it isn't overridden by config. */
393 if (!val && strcmp(tunnel, "ssh") == 0)
395 /* Killing the tunnel agent with SIGTERM leads to unsightly
396 * stderr output from ssh, unless we pass -q.
397 * The "-q" option to ssh is widely supported: all versions of
398 * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
399 * versions have it too. If the user is using some other ssh
400 * implementation that doesn't accept it, they can override it
401 * in the [tunnels] section of the config. */
402 val = "$SVN_SSH ssh -q --";
406 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
407 _("Undefined tunnel scheme '%s'"), tunnel);
409 /* If the scheme definition begins with "$varname", it means there
410 * is an environment variable which can override the command. */
414 len = strcspn(val, " ");
415 var = apr_pstrmemdup(pool, val, len);
423 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
424 _("Tunnel scheme %s requires environment "
425 "variable %s to be defined"), tunnel,
432 /* Tokenize the command into a list of arguments. */
433 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
434 if (status != APR_SUCCESS)
435 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
437 /* Calc number of the fixed arguments. */
438 for (n = 0; cmd_argv[n] != NULL; n++)
441 argv = apr_palloc(pool, (n + 4) * sizeof(char *));
443 /* Append the fixed arguments to the result. */
444 for (n = 0; cmd_argv[n] != NULL; n++)
445 argv[n] = cmd_argv[n];
447 argv[n++] = hostinfo;
448 argv[n++] = "svnserve";
456 /* This function handles any errors which occur in the child process
457 * created for a tunnel agent. We write the error out as a command
458 * failure; the code in ra_svn_open() to read the server's greeting
459 * will see the error and return it to the caller. */
460 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
463 svn_ra_svn_conn_t *conn;
464 apr_file_t *in_file, *out_file;
465 svn_stream_t *in_stream, *out_stream;
468 if (apr_file_open_stdin(&in_file, pool)
469 || apr_file_open_stdout(&out_file, pool))
472 in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool);
473 out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool);
475 conn = svn_ra_svn_create_conn5(NULL, in_stream, out_stream,
476 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
478 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
479 svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
480 svn_error_clear(err);
481 svn_error_clear(svn_ra_svn__flush(conn, pool));
484 /* (Note: *CONN is an output parameter.) */
485 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
490 apr_procattr_t *attr;
493 status = apr_procattr_create(&attr, pool);
494 if (status == APR_SUCCESS)
495 status = apr_procattr_io_set(attr, 1, 1, 0);
496 if (status == APR_SUCCESS)
497 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
498 if (status == APR_SUCCESS)
499 status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
500 proc = apr_palloc(pool, sizeof(*proc));
501 if (status == APR_SUCCESS)
502 status = apr_proc_create(proc, *args, args, NULL, attr, pool);
503 if (status != APR_SUCCESS)
504 return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
505 svn_error_wrap_apr(status,
506 _("Can't create tunnel")), NULL);
508 /* Arrange for the tunnel agent to get a SIGTERM on pool
509 * cleanup. This is a little extreme, but the alternatives
510 * weren't working out.
512 * Closing the pipes and waiting for the process to die
513 * was prone to mysterious hangs which are difficult to
514 * diagnose (e.g. svnserve dumps core due to unrelated bug;
515 * sshd goes into zombie state; ssh connection is never
516 * closed; ssh never terminates).
517 * See also the long dicussion in issue #2580 if you really
518 * want to know various reasons for these problems and
519 * the different opinions on this issue.
521 * On Win32, APR does not support KILL_ONLY_ONCE. It only has
522 * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
523 * KILL_ALWAYS, which immediately calls TerminateProcess().
524 * This instantly kills the tunnel, leaving sshd and svnserve
525 * on a remote machine running indefinitely. These processes
526 * accumulate. The problem is most often seen with a fast client
527 * machine and a modest internet connection, as the tunnel
528 * is killed before being able to gracefully complete the
529 * session. In that case, svn is unusable 100% of the time on
530 * the windows machine. Thus, on Win32, we use KILL_NEVER and
531 * take the lesser of two evils.
534 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
536 apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
539 /* APR pipe objects inherit by default. But we don't want the
540 * tunnel agent's pipes held open by future child processes
541 * (such as other ra_svn sessions), so turn that off. */
542 apr_file_inherit_unset(proc->in);
543 apr_file_inherit_unset(proc->out);
545 /* Guard against dotfile output to stdout on the server. */
546 *conn = svn_ra_svn_create_conn5(NULL,
547 svn_stream_from_aprfile2(proc->out, FALSE,
549 svn_stream_from_aprfile2(proc->in, FALSE,
551 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
553 err = svn_ra_svn__skip_leading_garbage(*conn, pool);
555 return svn_error_quick_wrap(
557 _("To better debug SSH connection problems, remove the -q "
558 "option from 'ssh' in the [tunnels] section of your "
559 "Subversion configuration file."));
564 /* Parse URL inot URI, validating it and setting the default port if none
565 was given. Allocate the URI fileds out of POOL. */
566 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
569 apr_status_t apr_err;
571 apr_err = apr_uri_parse(pool, url, uri);
574 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
575 _("Illegal svn repository URL '%s'"), url);
580 /* This structure is used as a baton for the pool cleanup function to
581 store tunnel parameters used by the close-tunnel callback. */
582 struct tunnel_data_t {
583 void *tunnel_context;
585 svn_ra_close_tunnel_func_t close_tunnel;
586 svn_stream_t *request;
587 svn_stream_t *response;
590 /* Pool cleanup function that invokes the close-tunnel callback. */
591 static apr_status_t close_tunnel_cleanup(void *baton)
593 const struct tunnel_data_t *const td = baton;
595 if (td->close_tunnel)
596 td->close_tunnel(td->tunnel_context, td->tunnel_baton);
598 svn_error_clear(svn_stream_close(td->request));
600 /* We might have one stream to use for both request and response! */
601 if (td->request != td->response)
602 svn_error_clear(svn_stream_close(td->response));
604 return APR_SUCCESS; /* ignored */
607 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
608 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
609 are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL,
610 it is the name of the tunnel type parsed from the URL scheme.
611 If TUNNEL_ARGV is not NULL, it points to a program argument list to use
612 when invoking the tunnel agent.
614 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
616 const apr_uri_t *uri,
617 const char *tunnel_name,
618 const char **tunnel_argv,
620 const svn_ra_callbacks2_t *callbacks,
621 void *callbacks_baton,
622 svn_auth_baton_t *auth_baton,
623 apr_pool_t *result_pool,
624 apr_pool_t *scratch_pool)
626 svn_ra_svn__session_baton_t *sess;
627 svn_ra_svn_conn_t *conn;
629 apr_uint64_t minver, maxver;
630 svn_ra_svn__list_t *mechlist, *server_caplist, *repos_caplist;
631 const char *client_string = NULL;
632 apr_pool_t *pool = result_pool;
633 svn_ra_svn__parent_t *parent;
635 parent = apr_pcalloc(pool, sizeof(*parent));
636 parent->client_url = svn_stringbuf_create(url, pool);
637 parent->server_url = svn_stringbuf_create(url, pool);
638 parent->path = svn_stringbuf_create_empty(pool);
640 sess = apr_palloc(pool, sizeof(*sess));
642 sess->is_tunneled = (tunnel_name != NULL);
643 sess->parent = parent;
644 sess->user = uri->user;
645 sess->hostname = uri->hostname;
646 sess->tunnel_name = tunnel_name;
647 sess->tunnel_argv = tunnel_argv;
648 sess->callbacks = callbacks;
649 sess->callbacks_baton = callbacks_baton;
650 sess->bytes_read = sess->bytes_written = 0;
651 sess->auth_baton = auth_baton;
654 SVN_ERR(svn_config_copy_config(&sess->config, config, pool));
660 sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>",
662 uri->hostname, uri->port);
665 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
668 struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td));
670 td->tunnel_baton = callbacks->tunnel_baton;
671 td->close_tunnel = NULL;
673 SVN_ERR(callbacks->open_tunnel_func(
674 &td->request, &td->response,
675 &td->close_tunnel, &td->tunnel_context,
676 callbacks->tunnel_baton, tunnel_name,
677 uri->user, uri->hostname, uri->port,
678 callbacks->cancel_func, callbacks_baton,
681 apr_pool_cleanup_register(pool, td, close_tunnel_cleanup,
682 apr_pool_cleanup_null);
684 conn = svn_ra_svn_create_conn5(NULL, td->response, td->request,
685 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
687 SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool));
692 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
693 uri->port ? uri->port : SVN_RA_SVN_PORT);
695 SVN_ERR(make_connection(uri->hostname,
696 uri->port ? uri->port : SVN_RA_SVN_PORT,
698 conn = svn_ra_svn_create_conn5(sock, NULL, NULL,
699 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
703 /* Build the useragent string, querying the client for any
704 customizations it wishes to note. For historical reasons, we
705 still deliver the hard-coded client version info
706 (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
707 separately in the protocol/capabilities handshake below. But the
708 commit logic wants the combined form for use with the
709 SVN_PROP_TXN_USER_AGENT ephemeral property because that's
710 consistent with our DAV approach. */
711 if (sess->callbacks->get_client_string != NULL)
712 SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
713 &client_string, pool));
715 sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
716 client_string, SVN_VA_NULL);
718 sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
720 /* Make sure we set conn->session before reading from it,
721 * because the reader and writer functions expect a non-NULL value. */
723 conn->session = sess;
725 /* Read server's greeting. */
726 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
727 &mechlist, &server_caplist));
729 /* We support protocol version 2. */
731 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
732 _("Server requires minimum version %d"),
735 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
736 _("Server only supports versions up to %d"),
738 SVN_ERR(svn_ra_svn__set_capabilities(conn, server_caplist));
740 /* All released versions of Subversion support edit-pipeline,
741 * so we do not support servers that do not. */
742 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
743 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
744 _("Server does not support edit pipelining"));
746 /* In protocol version 2, we send back our protocol version, our
747 * capability list, and the URL, and subsequently there is an auth
749 /* Client-side capabilities list: */
750 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwwww)cc(?c)",
752 SVN_RA_SVN_CAP_EDIT_PIPELINE,
753 SVN_RA_SVN_CAP_SVNDIFF1,
754 SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED,
755 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
756 SVN_RA_SVN_CAP_DEPTH,
757 SVN_RA_SVN_CAP_MERGEINFO,
758 SVN_RA_SVN_CAP_LOG_REVPROPS,
760 SVN_RA_SVN__DEFAULT_USERAGENT,
762 SVN_ERR(handle_auth_request(sess, pool));
764 /* This is where the security layer would go into effect if we
765 * supported security layers, which is a ways off. */
767 /* Read the repository's uuid and root URL, and perhaps learn more
768 capabilities that weren't available before now. */
769 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
770 &conn->repos_root, &repos_caplist));
772 SVN_ERR(svn_ra_svn__set_capabilities(conn, repos_caplist));
774 if (conn->repos_root)
776 conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
777 /* We should check that the returned string is a prefix of url, since
778 that's the API guarantee, but this isn't true for 1.0 servers.
779 Checking the length prevents client crashes. */
780 if (strlen(conn->repos_root) > strlen(url))
781 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
782 _("Impossibly long repository root from "
793 #define RA_SVN_DESCRIPTION \
794 N_("Module for accessing a repository using the svn network protocol.\n" \
795 " - with Cyrus SASL authentication")
797 #define RA_SVN_DESCRIPTION \
798 N_("Module for accessing a repository using the svn network protocol.")
801 static const char *ra_svn_get_description(apr_pool_t *pool)
803 return _(RA_SVN_DESCRIPTION);
806 static const char * const *
807 ra_svn_get_schemes(apr_pool_t *pool)
809 static const char *schemes[] = { "svn", NULL };
815 /* A simple whitelist to ensure the following are valid:
821 * with an extra restriction that a leading '-' is invalid.
824 is_valid_hostinfo(const char *hostinfo)
826 const char *p = hostinfo;
833 if (!svn_ctype_isalnum(*p) && !strchr(":.-_[]@", *p))
842 static svn_error_t *ra_svn_open(svn_ra_session_t *session,
843 const char **corrected_url,
845 const svn_ra_callbacks2_t *callbacks,
846 void *callback_baton,
847 svn_auth_baton_t *auth_baton,
849 apr_pool_t *result_pool,
850 apr_pool_t *scratch_pool)
852 apr_pool_t *sess_pool = svn_pool_create(result_pool);
853 svn_ra_svn__session_baton_t *sess;
854 const char *tunnel, **tunnel_argv;
856 svn_config_t *cfg, *cfg_client;
858 /* We don't support server-prescribed redirections in ra-svn. */
860 *corrected_url = NULL;
862 SVN_ERR(parse_url(url, &uri, sess_pool));
864 parse_tunnel(url, &tunnel, result_pool);
866 /* Use the default tunnel implementation if we got a tunnel name,
867 but either do not have tunnel handler callbacks installed, or
868 the handlers don't like the tunnel name. */
870 && (!callbacks->open_tunnel_func
871 || (callbacks->check_tunnel_func && callbacks->open_tunnel_func
872 && !callbacks->check_tunnel_func(callbacks->tunnel_baton,
875 const char *decoded_hostinfo;
877 decoded_hostinfo = svn_path_uri_decode(uri.hostinfo, result_pool);
879 if (!is_valid_hostinfo(decoded_hostinfo))
880 return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Invalid host '%s'"),
883 SVN_ERR(find_tunnel_agent(tunnel, decoded_hostinfo, &tunnel_argv,
884 config, result_pool));
890 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
892 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
893 svn_auth_set_parameter(auth_baton,
894 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
895 svn_auth_set_parameter(auth_baton,
896 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
898 /* We open the session in a subpool so we can get rid of it if we
899 reparent with a server that doesn't support reparenting. */
900 SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config,
901 callbacks, callback_baton,
902 auth_baton, sess_pool, scratch_pool));
903 session->priv = sess;
908 static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session,
909 svn_ra_session_t *old_session,
910 const char *new_session_url,
911 apr_pool_t *result_pool,
912 apr_pool_t *scratch_pool)
914 svn_ra_svn__session_baton_t *old_sess = old_session->priv;
916 SVN_ERR(ra_svn_open(new_session, NULL, new_session_url,
917 old_sess->callbacks, old_sess->callbacks_baton,
918 old_sess->auth_baton, old_sess->config,
919 result_pool, scratch_pool));
924 /* Send the "reparent to URL" command to the server for RA_SESSION and
925 update the session state. Use SCRATCH_POOL for tempoaries.
928 reparent_server(svn_ra_session_t *ra_session,
930 apr_pool_t *scratch_pool)
932 svn_ra_svn__session_baton_t *sess = ra_session->priv;
933 svn_ra_svn__parent_t *parent = sess->parent;
934 svn_ra_svn_conn_t *conn = sess->conn;
936 apr_pool_t *sess_pool;
937 svn_ra_svn__session_baton_t *new_sess;
940 /* Send the request to the server. */
941 SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, scratch_pool, url));
942 err = handle_auth_request(sess, scratch_pool);
945 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, ""));
946 svn_stringbuf_set(parent->server_url, url);
949 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
952 /* Servers before 1.4 doesn't support this command; try to reconnect
954 svn_error_clear(err);
955 /* Create a new subpool of the RA session pool. */
956 sess_pool = svn_pool_create(ra_session->pool);
957 err = parse_url(url, &uri, sess_pool);
959 err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv,
960 sess->config, sess->callbacks, sess->callbacks_baton,
961 sess->auth_baton, sess_pool, sess_pool);
962 /* We destroy the new session pool on error, since it is allocated in
963 the main session pool. */
966 svn_pool_destroy(sess_pool);
970 /* We have a new connection, assign it and destroy the old. */
971 ra_session->priv = new_sess;
972 svn_pool_destroy(sess->pool);
977 /* Make sure that RA_SESSION's client and server-side parent infp are in
978 sync. Use SCRATCH_POOL for temporary allocations. */
980 ensure_exact_server_parent(svn_ra_session_t *ra_session,
981 apr_pool_t *scratch_pool)
983 svn_ra_svn__session_baton_t *sess = ra_session->priv;
984 svn_ra_svn__parent_t *parent = sess->parent;
986 /* During e.g. a checkout operation, many requests will be sent for the
987 same URL that was used to create the session. So, both sides are
988 often already in sync. */
989 if (svn_stringbuf_compare(parent->client_url, parent->server_url))
992 /* Actually reparent the server to the session URL. */
993 SVN_ERR(reparent_server(ra_session, parent->client_url->data,
995 svn_stringbuf_setempty(parent->path);
1000 /* Return a copy of PATH, adjusted to the RA_SESSION's server parent URL.
1001 Allocate the result in RESULT_POOL. */
1003 reparent_path(svn_ra_session_t *ra_session,
1005 apr_pool_t *result_pool)
1007 svn_ra_svn__session_baton_t *sess = ra_session->priv;
1008 svn_ra_svn__parent_t *parent = sess->parent;
1010 return svn_relpath_join(parent->path->data, path, result_pool);
1013 /* Return a copy of PATHS, containing the same const char * paths but
1014 adjusted to the RA_SESSION's server parent URL. Returns NULL if
1015 PATHS is NULL. Allocate the result in RESULT_POOL. */
1016 static apr_array_header_t *
1017 reparent_path_array(svn_ra_session_t *ra_session,
1018 const apr_array_header_t *paths,
1019 apr_pool_t *result_pool)
1022 apr_array_header_t *result;
1027 result = apr_array_copy(result_pool, paths);
1028 for (i = 0; i < result->nelts; ++i)
1030 const char **path = &APR_ARRAY_IDX(result, i, const char *);
1031 *path = reparent_path(ra_session, *path, result_pool);
1037 /* Return a copy of PATHS, containing the same paths for keys but adjusted
1038 to the RA_SESSION's server parent URL. Keeps the values as-are and
1039 returns NULL if PATHS is NULL. Allocate the result in RESULT_POOL. */
1041 reparent_path_hash(svn_ra_session_t *ra_session,
1043 apr_pool_t *result_pool,
1044 apr_pool_t *scratch_pool)
1047 apr_hash_index_t *hi;
1052 result = svn_hash__make(result_pool);
1053 for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi))
1055 const char *path = apr_hash_this_key(hi);
1056 svn_hash_sets(result,
1057 reparent_path(ra_session, path, result_pool),
1058 apr_hash_this_val(hi));
1064 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
1068 svn_ra_svn__session_baton_t *sess = ra_session->priv;
1069 svn_ra_svn__parent_t *parent = sess->parent;
1070 svn_ra_svn_conn_t *conn = sess->conn;
1073 /* Eliminate reparent requests if they are to a sub-path of the
1074 server's current parent path. */
1075 path = svn_uri_skip_ancestor(parent->server_url->data, url, pool);
1078 /* Send the request to the server.
1080 If within the same repository, reparent to the repo root
1081 because this will maximize the chance to turn future reparent
1082 requests into a client-side update of the rel path. */
1083 path = conn->repos_root
1084 ? svn_uri_skip_ancestor(conn->repos_root, url, pool)
1088 SVN_ERR(reparent_server(ra_session, conn->repos_root, pool));
1090 SVN_ERR(reparent_server(ra_session, url, pool));
1093 /* Update the local PARENT information.
1094 PARENT.SERVER_BASE_URL is already up-to-date. */
1095 svn_stringbuf_set(parent->client_url, url);
1097 svn_stringbuf_set(parent->path, path);
1099 svn_stringbuf_setempty(parent->path);
1101 return SVN_NO_ERROR;
1104 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
1108 svn_ra_svn__session_baton_t *sess = session->priv;
1109 svn_ra_svn__parent_t *parent = sess->parent;
1111 *url = apr_pstrmemdup(pool, parent->client_url->data,
1112 parent->client_url->len);
1114 return SVN_NO_ERROR;
1117 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
1118 svn_revnum_t *rev, apr_pool_t *pool)
1120 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1121 svn_ra_svn_conn_t *conn = sess_baton->conn;
1123 SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
1124 SVN_ERR(handle_auth_request(sess_baton, pool));
1125 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
1126 return SVN_NO_ERROR;
1129 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
1130 svn_revnum_t *rev, apr_time_t tm,
1133 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1134 svn_ra_svn_conn_t *conn = sess_baton->conn;
1136 SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
1137 SVN_ERR(handle_auth_request(sess_baton, pool));
1138 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
1139 return SVN_NO_ERROR;
1142 /* Forward declaration. */
1143 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
1145 const char *capability,
1148 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1150 const svn_string_t *const *old_value_p,
1151 const svn_string_t *value,
1154 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1155 svn_ra_svn_conn_t *conn = sess_baton->conn;
1156 svn_boolean_t dont_care;
1157 const svn_string_t *old_value;
1158 svn_boolean_t has_atomic_revprops;
1160 SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
1161 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
1166 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
1167 SVN_ERR_ASSERT(has_atomic_revprops);
1170 old_value = *old_value_p;
1178 if (has_atomic_revprops)
1179 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
1183 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
1186 SVN_ERR(handle_auth_request(sess_baton, pool));
1187 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1188 return SVN_NO_ERROR;
1191 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
1194 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1195 svn_ra_svn_conn_t *conn = sess_baton->conn;
1198 return SVN_NO_ERROR;
1201 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
1204 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1205 svn_ra_svn_conn_t *conn = sess_baton->conn;
1207 if (!conn->repos_root)
1208 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
1209 _("Server did not send repository root"));
1210 *url = conn->repos_root;
1211 return SVN_NO_ERROR;
1214 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
1215 apr_hash_t **props, apr_pool_t *pool)
1217 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1218 svn_ra_svn_conn_t *conn = sess_baton->conn;
1219 svn_ra_svn__list_t *proplist;
1221 SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
1222 SVN_ERR(handle_auth_request(sess_baton, pool));
1223 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
1224 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1225 return SVN_NO_ERROR;
1228 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1230 svn_string_t **value, apr_pool_t *pool)
1232 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1233 svn_ra_svn_conn_t *conn = sess_baton->conn;
1235 SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
1236 SVN_ERR(handle_auth_request(sess_baton, pool));
1237 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
1238 return SVN_NO_ERROR;
1241 static svn_error_t *ra_svn_end_commit(void *baton)
1243 ra_svn_commit_callback_baton_t *ccb = baton;
1244 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
1246 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
1247 SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
1249 &(commit_info->revision),
1250 &(commit_info->date),
1251 &(commit_info->author),
1252 &(commit_info->post_commit_err)));
1254 commit_info->repos_root = apr_pstrdup(ccb->pool,
1255 ccb->sess_baton->conn->repos_root);
1258 SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
1260 return SVN_NO_ERROR;
1263 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
1264 const svn_delta_editor_t **editor,
1266 apr_hash_t *revprop_table,
1267 svn_commit_callback2_t callback,
1268 void *callback_baton,
1269 apr_hash_t *lock_tokens,
1270 svn_boolean_t keep_locks,
1273 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1274 svn_ra_svn_conn_t *conn = sess_baton->conn;
1275 ra_svn_commit_callback_baton_t *ccb;
1276 apr_hash_index_t *hi;
1277 apr_pool_t *iterpool;
1278 const svn_string_t *log_msg = svn_hash_gets(revprop_table,
1279 SVN_PROP_REVISION_LOG);
1281 if (log_msg == NULL &&
1282 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1284 return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
1285 _("ra_svn does not support not specifying "
1286 "a log message with pre-1.5 servers; "
1287 "consider passing an empty one, or upgrading "
1290 else if (log_msg == NULL)
1291 /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument
1292 to the 'commit' protocol command is non-optional; on the server side,
1293 only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The
1294 "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
1295 will have a NULL log message (not just "", really NULL).
1297 svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
1298 present; this was elevated to a protocol promise in r1498550 (and
1299 later documented in this comment) in order to fix the segmentation
1300 fault bug described in the log message of r1498550.*/
1301 log_msg = svn_string_create("", pool);
1303 /* If we're sending revprops other than svn:log, make sure the server won't
1304 silently ignore them. */
1305 if (apr_hash_count(revprop_table) > 1 &&
1306 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1307 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1308 _("Server doesn't support setting arbitrary "
1309 "revision properties during commit"));
1311 /* If the server supports ephemeral txnprops, add the one that
1312 reports the client's version level string. */
1313 if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1314 svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1316 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1317 svn_string_create(SVN_VER_NUMBER, pool));
1318 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1319 svn_string_create(sess_baton->useragent, pool));
1322 /* Callbacks may assume that all data is relative the sessions's URL. */
1323 SVN_ERR(ensure_exact_server_parent(session, pool));
1325 /* Tell the server we're starting the commit.
1326 Send log message here for backwards compatibility with servers
1328 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1332 iterpool = svn_pool_create(pool);
1333 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1337 const char *path, *token;
1339 svn_pool_clear(iterpool);
1340 apr_hash_this(hi, &key, NULL, &val);
1343 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1345 svn_pool_destroy(iterpool);
1347 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1348 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1349 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1350 SVN_ERR(handle_auth_request(sess_baton, pool));
1351 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1353 /* Remember a few arguments for when the commit is over. */
1354 ccb = apr_palloc(pool, sizeof(*ccb));
1355 ccb->sess_baton = sess_baton;
1357 ccb->new_rev = NULL;
1358 ccb->callback = callback;
1359 ccb->callback_baton = callback_baton;
1361 /* Fetch an editor for the caller to drive. The editor will call
1362 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1363 * in the new_rev, committed_date, and committed_author values. */
1364 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1365 ra_svn_end_commit, ccb);
1366 return SVN_NO_ERROR;
1369 /* Parse IPROPLIST, an array of svn_ra_svn__item_t structures, as a list of
1370 const char * repos relative paths and properties for those paths, storing
1371 the result as an array of svn_prop_inherited_item_t *items. */
1372 static svn_error_t *
1373 parse_iproplist(apr_array_header_t **inherited_props,
1374 const svn_ra_svn__list_t *iproplist,
1375 svn_ra_session_t *session,
1376 apr_pool_t *result_pool,
1377 apr_pool_t *scratch_pool)
1381 apr_pool_t *iterpool;
1383 if (iproplist == NULL)
1385 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1386 capability we shouldn't be asking for inherited props, but if we
1387 did and the server sent back nothing then we'll want to handle
1389 *inherited_props = NULL;
1390 return SVN_NO_ERROR;
1393 *inherited_props = apr_array_make(
1394 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1396 iterpool = svn_pool_create(scratch_pool);
1398 for (i = 0; i < iproplist->nelts; i++)
1400 svn_ra_svn__list_t *iprop_list;
1401 char *parent_rel_path;
1403 apr_hash_index_t *hi;
1404 svn_prop_inherited_item_t *new_iprop =
1405 apr_palloc(result_pool, sizeof(*new_iprop));
1406 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(iproplist, i);
1407 if (elt->kind != SVN_RA_SVN_LIST)
1408 return svn_error_create(
1409 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1410 _("Inherited proplist element not a list"));
1412 svn_pool_clear(iterpool);
1414 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cl",
1415 &parent_rel_path, &iprop_list));
1416 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1417 new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path);
1418 new_iprop->prop_hash = svn_hash__make(result_pool);
1419 for (hi = apr_hash_first(iterpool, iprops);
1421 hi = apr_hash_next(hi))
1423 const char *name = apr_hash_this_key(hi);
1424 svn_string_t *value = apr_hash_this_val(hi);
1425 svn_hash_sets(new_iprop->prop_hash,
1426 apr_pstrdup(result_pool, name),
1427 svn_string_dup(value, result_pool));
1429 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1432 svn_pool_destroy(iterpool);
1433 return SVN_NO_ERROR;
1436 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1437 svn_revnum_t rev, svn_stream_t *stream,
1438 svn_revnum_t *fetched_rev,
1442 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1443 svn_ra_svn_conn_t *conn = sess_baton->conn;
1444 svn_ra_svn__list_t *proplist;
1445 const char *expected_digest;
1446 svn_checksum_t *expected_checksum = NULL;
1447 svn_checksum_ctx_t *checksum_ctx;
1448 apr_pool_t *iterpool;
1450 path = reparent_path(session, path, pool);
1451 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1452 (props != NULL), (stream != NULL)));
1453 SVN_ERR(handle_auth_request(sess_baton, pool));
1454 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1461 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1463 /* We're done if the contents weren't wanted. */
1465 return SVN_NO_ERROR;
1467 if (expected_digest)
1469 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1470 expected_digest, pool));
1471 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1474 /* Read the file's contents. */
1475 iterpool = svn_pool_create(pool);
1478 svn_ra_svn__item_t *item;
1480 svn_pool_clear(iterpool);
1481 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1482 if (item->kind != SVN_RA_SVN_STRING)
1483 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1484 _("Non-string as part of file contents"));
1485 if (item->u.string.len == 0)
1488 if (expected_checksum)
1489 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string.data,
1490 item->u.string.len));
1492 SVN_ERR(svn_stream_write(stream, item->u.string.data,
1493 &item->u.string.len));
1495 svn_pool_destroy(iterpool);
1497 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1499 if (expected_checksum)
1501 svn_checksum_t *checksum;
1503 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1504 if (!svn_checksum_match(checksum, expected_checksum))
1505 return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1506 _("Checksum mismatch for '%s'"),
1510 return SVN_NO_ERROR;
1513 /* Write the protocol words that correspond to DIRENT_FIELDS to CONN
1514 * and use SCRATCH_POOL for temporary allocations. */
1515 static svn_error_t *
1516 send_dirent_fields(svn_ra_svn_conn_t *conn,
1517 apr_uint32_t dirent_fields,
1518 apr_pool_t *scratch_pool)
1520 if (dirent_fields & SVN_DIRENT_KIND)
1521 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
1522 SVN_RA_SVN_DIRENT_KIND));
1523 if (dirent_fields & SVN_DIRENT_SIZE)
1524 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
1525 SVN_RA_SVN_DIRENT_SIZE));
1526 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1527 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
1528 SVN_RA_SVN_DIRENT_HAS_PROPS));
1529 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1530 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
1531 SVN_RA_SVN_DIRENT_CREATED_REV));
1532 if (dirent_fields & SVN_DIRENT_TIME)
1533 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
1534 SVN_RA_SVN_DIRENT_TIME));
1535 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1536 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
1537 SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1539 return SVN_NO_ERROR;
1542 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1543 apr_hash_t **dirents,
1544 svn_revnum_t *fetched_rev,
1548 apr_uint32_t dirent_fields,
1551 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1552 svn_ra_svn_conn_t *conn = sess_baton->conn;
1553 svn_ra_svn__list_t *proplist, *dirlist;
1556 path = reparent_path(session, path, pool);
1557 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1558 rev, (props != NULL), (dirents != NULL)));
1559 SVN_ERR(send_dirent_fields(conn, dirent_fields, pool));
1561 /* Always send the, nominally optional, want-iprops as "false" to
1562 workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1563 to see "true" if it is omitted. */
1564 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1566 SVN_ERR(handle_auth_request(sess_baton, pool));
1567 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1573 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1575 /* We're done if dirents aren't wanted. */
1577 return SVN_NO_ERROR;
1579 /* Interpret the directory list. */
1580 *dirents = svn_hash__make(pool);
1581 for (i = 0; i < dirlist->nelts; i++)
1583 const char *name, *kind, *cdate, *cauthor;
1584 svn_boolean_t has_props;
1585 svn_dirent_t *dirent;
1588 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(dirlist, i);
1590 if (elt->kind != SVN_RA_SVN_LIST)
1591 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1592 _("Dirlist element not a list"));
1593 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cwnbr(?c)(?c)",
1594 &name, &kind, &size, &has_props,
1595 &crev, &cdate, &cauthor));
1597 /* Nothing to sanitize here. Any multi-segment path is simply
1598 illegal in the hash returned by svn_ra_get_dir2. */
1599 if (strchr(name, '/'))
1600 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1601 _("Invalid directory entry name '%s'"),
1604 dirent = svn_dirent_create(pool);
1605 dirent->kind = svn_node_kind_from_word(kind);
1606 dirent->size = size;/* FIXME: svn_filesize_t */
1607 dirent->has_props = has_props;
1608 dirent->created_rev = crev;
1609 /* NOTE: the tuple's format string says CDATE may be NULL. But this
1610 function does not allow that. The server has always sent us some
1611 random date, however, so this just happens to work. But let's
1612 be wary of servers that are (improperly) fixed to send NULL.
1614 Note: they should NOT be "fixed" to send NULL, as that would break
1615 any older clients which received that NULL. But we may as well
1616 be defensive against a malicous server. */
1620 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1621 dirent->last_author = cauthor;
1622 svn_hash_sets(*dirents, name, dirent);
1625 return SVN_NO_ERROR;
1628 /* Converts a apr_uint64_t with values TRUE, FALSE or
1629 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1630 to a svn_tristate_t */
1631 static svn_tristate_t
1632 optbool_to_tristate(apr_uint64_t v)
1634 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
1635 return svn_tristate_true;
1637 return svn_tristate_false;
1639 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1642 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1643 server, which defaults to youngest. */
1644 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1645 svn_mergeinfo_catalog_t *catalog,
1646 const apr_array_header_t *paths,
1647 svn_revnum_t revision,
1648 svn_mergeinfo_inheritance_t inherit,
1649 svn_boolean_t include_descendants,
1652 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1653 svn_ra_svn__parent_t *parent = sess_baton->parent;
1654 svn_ra_svn_conn_t *conn = sess_baton->conn;
1656 svn_ra_svn__list_t *mergeinfo_tuple;
1657 svn_ra_svn__item_t *elt;
1660 paths = reparent_path_array(session, paths, pool);
1661 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1662 for (i = 0; i < paths->nelts; i++)
1664 path = APR_ARRAY_IDX(paths, i, const char *);
1665 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1667 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1668 svn_inheritance_to_word(inherit),
1669 include_descendants));
1671 SVN_ERR(handle_auth_request(sess_baton, pool));
1672 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1675 if (mergeinfo_tuple->nelts > 0)
1677 *catalog = svn_hash__make(pool);
1678 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1680 svn_mergeinfo_t for_path;
1681 const char *to_parse;
1683 elt = &SVN_RA_SVN__LIST_ITEM(mergeinfo_tuple, i);
1684 if (elt->kind != SVN_RA_SVN_LIST)
1685 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1686 _("Mergeinfo element is not a list"));
1687 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cc",
1689 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1691 /* Correct for naughty servers that send "relative" paths
1692 with leading slashes! */
1696 /* Correct for the (potential) difference between client and
1697 server-side session parent paths. */
1698 path = svn_relpath_skip_ancestor(parent->path->data, path);
1699 svn_hash_sets(*catalog, path, for_path);
1703 return SVN_NO_ERROR;
1706 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1707 const svn_ra_reporter3_t **reporter,
1708 void **report_baton, svn_revnum_t rev,
1709 const char *target, svn_depth_t depth,
1710 svn_boolean_t send_copyfrom_args,
1711 svn_boolean_t ignore_ancestry,
1712 const svn_delta_editor_t *update_editor,
1715 apr_pool_t *scratch_pool)
1717 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1718 svn_ra_svn_conn_t *conn = sess_baton->conn;
1719 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1721 /* Callbacks may assume that all data is relative the sessions's URL. */
1722 SVN_ERR(ensure_exact_server_parent(session, scratch_pool));
1724 /* Tell the server we want to start an update. */
1725 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1726 depth, send_copyfrom_args,
1728 SVN_ERR(handle_auth_request(sess_baton, pool));
1730 /* Fetch a reporter for the caller to drive. The reporter will drive
1731 * update_editor upon finish_report(). */
1732 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1733 target, depth, reporter, report_baton));
1734 return SVN_NO_ERROR;
1737 static svn_error_t *
1738 ra_svn_switch(svn_ra_session_t *session,
1739 const svn_ra_reporter3_t **reporter,
1740 void **report_baton, svn_revnum_t rev,
1741 const char *target, svn_depth_t depth,
1742 const char *switch_url,
1743 svn_boolean_t send_copyfrom_args,
1744 svn_boolean_t ignore_ancestry,
1745 const svn_delta_editor_t *update_editor,
1747 apr_pool_t *result_pool,
1748 apr_pool_t *scratch_pool)
1750 apr_pool_t *pool = result_pool;
1751 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1752 svn_ra_svn_conn_t *conn = sess_baton->conn;
1753 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1755 /* Callbacks may assume that all data is relative the sessions's URL. */
1756 SVN_ERR(ensure_exact_server_parent(session, scratch_pool));
1758 /* Tell the server we want to start a switch. */
1759 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1761 send_copyfrom_args, ignore_ancestry));
1762 SVN_ERR(handle_auth_request(sess_baton, pool));
1764 /* Fetch a reporter for the caller to drive. The reporter will drive
1765 * update_editor upon finish_report(). */
1766 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1767 target, depth, reporter, report_baton));
1768 return SVN_NO_ERROR;
1771 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1772 const svn_ra_reporter3_t **reporter,
1773 void **report_baton,
1774 const char *target, svn_revnum_t rev,
1776 const svn_delta_editor_t *status_editor,
1777 void *status_baton, apr_pool_t *pool)
1779 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1780 svn_ra_svn_conn_t *conn = sess_baton->conn;
1781 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1783 /* Callbacks may assume that all data is relative the sessions's URL. */
1784 SVN_ERR(ensure_exact_server_parent(session, pool));
1786 /* Tell the server we want to start a status operation. */
1787 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1789 SVN_ERR(handle_auth_request(sess_baton, pool));
1791 /* Fetch a reporter for the caller to drive. The reporter will drive
1792 * status_editor upon finish_report(). */
1793 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1794 target, depth, reporter, report_baton));
1795 return SVN_NO_ERROR;
1798 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1799 const svn_ra_reporter3_t **reporter,
1800 void **report_baton,
1801 svn_revnum_t rev, const char *target,
1803 svn_boolean_t ignore_ancestry,
1804 svn_boolean_t text_deltas,
1805 const char *versus_url,
1806 const svn_delta_editor_t *diff_editor,
1807 void *diff_baton, apr_pool_t *pool)
1809 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1810 svn_ra_svn_conn_t *conn = sess_baton->conn;
1811 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1813 /* Callbacks may assume that all data is relative the sessions's URL. */
1814 SVN_ERR(ensure_exact_server_parent(session, pool));
1816 /* Tell the server we want to start a diff. */
1817 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1818 ignore_ancestry, versus_url,
1819 text_deltas, depth));
1820 SVN_ERR(handle_auth_request(sess_baton, pool));
1822 /* Fetch a reporter for the caller to drive. The reporter will drive
1823 * diff_editor upon finish_report(). */
1824 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1825 target, depth, reporter, report_baton));
1826 return SVN_NO_ERROR;
1829 /* Return TRUE if ITEM matches the word "done". */
1830 static svn_boolean_t
1831 is_done_response(const svn_ra_svn__item_t *item)
1833 static const svn_string_t str_done = SVN__STATIC_STRING("done");
1835 return ( item->kind == SVN_RA_SVN_WORD
1836 && svn_string_compare(&item->u.word, &str_done));
1840 static svn_error_t *
1841 perform_ra_svn_log(svn_error_t **outer_error,
1842 svn_ra_session_t *session,
1843 const apr_array_header_t *paths,
1844 svn_revnum_t start, svn_revnum_t end,
1846 svn_boolean_t discover_changed_paths,
1847 svn_boolean_t strict_node_history,
1848 svn_boolean_t include_merged_revisions,
1849 const apr_array_header_t *revprops,
1850 svn_log_entry_receiver_t receiver,
1851 void *receiver_baton,
1854 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1855 svn_ra_svn_conn_t *conn = sess_baton->conn;
1856 apr_pool_t *iterpool;
1861 svn_boolean_t want_custom_revprops;
1862 svn_boolean_t want_author = FALSE;
1863 svn_boolean_t want_message = FALSE;
1864 svn_boolean_t want_date = FALSE;
1867 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1870 for (i = 0; i < paths->nelts; i++)
1872 path = APR_ARRAY_IDX(paths, i, const char *);
1873 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1876 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1877 discover_changed_paths, strict_node_history,
1878 (apr_uint64_t) limit,
1879 include_merged_revisions));
1882 want_custom_revprops = FALSE;
1883 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1884 for (i = 0; i < revprops->nelts; i++)
1886 name = APR_ARRAY_IDX(revprops, i, char *);
1887 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1889 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1891 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1893 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1894 want_message = TRUE;
1896 want_custom_revprops = TRUE;
1898 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1902 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1906 want_message = TRUE;
1907 want_custom_revprops = TRUE;
1910 SVN_ERR(handle_auth_request(sess_baton, pool));
1912 /* Read the log messages. */
1913 iterpool = svn_pool_create(pool);
1916 apr_uint64_t has_children_param, invalid_revnum_param;
1917 apr_uint64_t has_subtractive_merge_param;
1918 svn_string_t *author, *date, *message;
1919 svn_ra_svn__list_t *cplist, *rplist;
1920 svn_log_entry_t *log_entry;
1921 svn_boolean_t has_children;
1922 svn_boolean_t subtractive_merge = FALSE;
1923 apr_uint64_t revprop_count;
1924 svn_ra_svn__item_t *item;
1928 svn_pool_clear(iterpool);
1929 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1930 if (is_done_response(item))
1932 if (item->kind != SVN_RA_SVN_LIST)
1933 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1934 _("Log entry not a list"));
1935 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list,
1936 "lr(?s)(?s)(?s)?BBnl?B",
1937 &cplist, &rev, &author, &date,
1938 &message, &has_children_param,
1939 &invalid_revnum_param,
1940 &revprop_count, &rplist,
1941 &has_subtractive_merge_param));
1942 if (want_custom_revprops && rplist == NULL)
1944 /* Caller asked for custom revprops, but server is too old. */
1945 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1946 _("Server does not support custom revprops"
1950 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1951 has_children = FALSE;
1953 has_children = (svn_boolean_t) has_children_param;
1955 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1956 subtractive_merge = FALSE;
1958 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1960 /* Because the svn protocol won't let us send an invalid revnum, we have
1961 to recover that fact using the extra parameter. */
1962 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1963 && invalid_revnum_param)
1964 rev = SVN_INVALID_REVNUM;
1966 if (cplist->nelts > 0)
1968 /* Interpret the changed-paths list. */
1969 cphash = svn_hash__make(iterpool);
1970 for (i = 0; i < cplist->nelts; i++)
1972 svn_log_changed_path2_t *change;
1973 svn_string_t *cpath;
1974 const char *copy_path, *action, *kind_str;
1975 apr_uint64_t text_mods, prop_mods;
1976 svn_revnum_t copy_rev;
1977 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(cplist, i);
1979 if (elt->kind != SVN_RA_SVN_LIST)
1980 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1981 _("Changed-path entry not a list"));
1982 SVN_ERR(svn_ra_svn__read_data_log_changed_entry(&elt->u.list,
1983 &cpath, &action, ©_path,
1984 ©_rev, &kind_str,
1985 &text_mods, &prop_mods));
1987 if (!svn_fspath__is_canonical(cpath->data))
1989 cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1990 cpath->len = strlen(cpath->data);
1992 if (copy_path && !svn_fspath__is_canonical(copy_path))
1993 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1995 change = svn_log_changed_path2_create(iterpool);
1996 change->action = *action;
1997 change->copyfrom_path = copy_path;
1998 change->copyfrom_rev = copy_rev;
1999 change->node_kind = svn_node_kind_from_word(kind_str);
2000 change->text_modified = optbool_to_tristate(text_mods);
2001 change->props_modified = optbool_to_tristate(prop_mods);
2002 apr_hash_set(cphash, cpath->data, cpath->len, change);
2009 - Except if the server sends more than a >= 1 limit top level items
2010 - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
2011 in an earlier invocation. */
2012 if (! ((limit > 0) && (nest_level == 0) && (++nreceived > limit))
2016 log_entry = svn_log_entry_create(iterpool);
2018 log_entry->changed_paths = cphash;
2019 log_entry->changed_paths2 = cphash;
2020 log_entry->revision = rev;
2021 log_entry->has_children = has_children;
2022 log_entry->subtractive_merge = subtractive_merge;
2024 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
2025 &log_entry->revprops));
2026 if (log_entry->revprops == NULL)
2027 log_entry->revprops = svn_hash__make(iterpool);
2029 if (author && want_author)
2030 svn_hash_sets(log_entry->revprops,
2031 SVN_PROP_REVISION_AUTHOR, author);
2032 if (date && want_date)
2033 svn_hash_sets(log_entry->revprops,
2034 SVN_PROP_REVISION_DATE, date);
2035 if (message && want_message)
2036 svn_hash_sets(log_entry->revprops,
2037 SVN_PROP_REVISION_LOG, message);
2039 err = receiver(receiver_baton, log_entry, iterpool);
2040 if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION))
2042 *outer_error = svn_error_trace(
2043 svn_error_compose_create(*outer_error, err));
2048 if (log_entry->has_children)
2052 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
2054 SVN_ERR_ASSERT(nest_level);
2059 svn_pool_destroy(iterpool);
2061 /* Read the response. */
2062 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
2065 static svn_error_t *
2066 ra_svn_log(svn_ra_session_t *session,
2067 const apr_array_header_t *paths,
2068 svn_revnum_t start, svn_revnum_t end,
2070 svn_boolean_t discover_changed_paths,
2071 svn_boolean_t strict_node_history,
2072 svn_boolean_t include_merged_revisions,
2073 const apr_array_header_t *revprops,
2074 svn_log_entry_receiver_t receiver,
2075 void *receiver_baton, apr_pool_t *pool)
2077 svn_error_t *outer_error = NULL;
2080 /* If we don't specify paths, the session's URL is implied.
2082 Because the paths passed to callbacks are always relative the repos
2083 root, there is no need *always* sync the parent URLs despite invoking
2084 user-provided callbacks. */
2086 paths = reparent_path_array(session, paths, pool);
2088 SVN_ERR(ensure_exact_server_parent(session, pool));
2090 err = svn_error_trace(perform_ra_svn_log(&outer_error,
2094 discover_changed_paths,
2095 strict_node_history,
2096 include_merged_revisions,
2098 receiver, receiver_baton,
2100 return svn_error_trace(
2101 svn_error_compose_create(outer_error,
2107 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
2108 const char *path, svn_revnum_t rev,
2109 svn_node_kind_t *kind, apr_pool_t *pool)
2111 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2112 svn_ra_svn_conn_t *conn = sess_baton->conn;
2113 const char *kind_word;
2115 path = reparent_path(session, path, pool);
2116 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
2117 SVN_ERR(handle_auth_request(sess_baton, pool));
2118 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
2119 *kind = svn_node_kind_from_word(kind_word);
2120 return SVN_NO_ERROR;
2124 /* If ERR is a command not supported error, wrap it in a
2125 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
2126 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
2129 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2130 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
2136 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
2137 const char *path, svn_revnum_t rev,
2138 svn_dirent_t **dirent, apr_pool_t *pool)
2140 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2141 svn_ra_svn_conn_t *conn = sess_baton->conn;
2142 svn_ra_svn__list_t *list = NULL;
2143 svn_dirent_t *the_dirent;
2145 path = reparent_path(session, path, pool);
2146 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
2147 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2148 N_("'stat' not implemented")));
2149 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2157 const char *kind, *cdate, *cauthor;
2158 svn_boolean_t has_props;
2162 SVN_ERR(svn_ra_svn__parse_tuple(list, "wnbr(?c)(?c)",
2163 &kind, &size, &has_props,
2164 &crev, &cdate, &cauthor));
2166 the_dirent = svn_dirent_create(pool);
2167 the_dirent->kind = svn_node_kind_from_word(kind);
2168 the_dirent->size = size;/* FIXME: svn_filesize_t */
2169 the_dirent->has_props = has_props;
2170 the_dirent->created_rev = crev;
2171 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
2172 the_dirent->last_author = cauthor;
2174 *dirent = the_dirent;
2177 return SVN_NO_ERROR;
2181 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
2182 apr_hash_t **locations,
2184 svn_revnum_t peg_revision,
2185 const apr_array_header_t *location_revisions,
2188 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2189 svn_ra_svn_conn_t *conn = sess_baton->conn;
2190 svn_revnum_t revision;
2191 svn_boolean_t is_done;
2192 apr_pool_t *iterpool;
2195 path = reparent_path(session, path, pool);
2197 /* Transmit the parameters. */
2198 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
2199 "get-locations", path, peg_revision));
2200 for (i = 0; i < location_revisions->nelts; i++)
2202 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
2203 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
2206 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2208 /* Servers before 1.1 don't support this command. Check for this here. */
2209 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2210 N_("'get-locations' not implemented")));
2212 /* Read the hash items. */
2214 *locations = apr_hash_make(pool);
2215 iterpool = svn_pool_create(pool);
2218 svn_ra_svn__item_t *item;
2219 const char *ret_path;
2221 svn_pool_clear(iterpool);
2222 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2223 if (is_done_response(item))
2225 else if (item->kind != SVN_RA_SVN_LIST)
2226 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2227 _("Location entry not a list"));
2230 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "rc",
2231 &revision, &ret_path));
2233 /* This also makes RET_PATH live in POOL rather than ITERPOOL. */
2234 ret_path = svn_fspath__canonicalize(ret_path, pool);
2235 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
2237 sizeof(revision), ret_path);
2241 svn_pool_destroy(iterpool);
2243 /* Read the response. This is so the server would have a chance to
2244 * report an error. */
2245 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
2248 static svn_error_t *
2249 perform_get_location_segments(svn_error_t **outer_error,
2250 svn_ra_session_t *session,
2252 svn_revnum_t peg_revision,
2253 svn_revnum_t start_rev,
2254 svn_revnum_t end_rev,
2255 svn_location_segment_receiver_t receiver,
2256 void *receiver_baton,
2259 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2260 svn_ra_svn_conn_t *conn = sess_baton->conn;
2261 svn_boolean_t is_done;
2262 apr_pool_t *iterpool = svn_pool_create(pool);
2264 /* Transmit the parameters. */
2265 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2266 "get-location-segments",
2267 path, peg_revision, start_rev, end_rev));
2269 /* Servers before 1.5 don't support this command. Check for this here. */
2270 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2271 N_("'get-location-segments'"
2272 " not implemented")));
2274 /* Parse the response. */
2278 svn_revnum_t range_start, range_end;
2279 svn_ra_svn__item_t *item;
2280 const char *ret_path;
2282 svn_pool_clear(iterpool);
2283 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2284 if (is_done_response(item))
2286 else if (item->kind != SVN_RA_SVN_LIST)
2287 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2288 _("Location segment entry not a list"));
2291 svn_location_segment_t *segment = apr_pcalloc(iterpool,
2293 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "rr(?c)",
2294 &range_start, &range_end, &ret_path));
2295 if (! (SVN_IS_VALID_REVNUM(range_start)
2296 && SVN_IS_VALID_REVNUM(range_end)))
2297 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2298 _("Expected valid revision range"));
2300 ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2301 segment->path = ret_path;
2302 segment->range_start = range_start;
2303 segment->range_end = range_end;
2307 svn_error_t *err = svn_error_trace(receiver(segment, receiver_baton,
2310 if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION))
2317 svn_pool_destroy(iterpool);
2319 /* Read the response. This is so the server would have a chance to
2320 * report an error. */
2321 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2323 return SVN_NO_ERROR;
2326 static svn_error_t *
2327 ra_svn_get_location_segments(svn_ra_session_t *session,
2329 svn_revnum_t peg_revision,
2330 svn_revnum_t start_rev,
2331 svn_revnum_t end_rev,
2332 svn_location_segment_receiver_t receiver,
2333 void *receiver_baton,
2336 svn_error_t *outer_err = SVN_NO_ERROR;
2339 path = reparent_path(session, path, pool);
2340 err = svn_error_trace(
2341 perform_get_location_segments(&outer_err, session, path,
2342 peg_revision, start_rev, end_rev,
2343 receiver, receiver_baton, pool));
2344 return svn_error_compose_create(outer_err, err);
2347 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2349 svn_revnum_t start, svn_revnum_t end,
2350 svn_boolean_t include_merged_revisions,
2351 svn_file_rev_handler_t handler,
2352 void *handler_baton, apr_pool_t *pool)
2354 svn_ra_svn__session_baton_t *sess_baton = session->priv;
2355 apr_pool_t *rev_pool, *chunk_pool;
2356 svn_boolean_t has_txdelta;
2357 svn_boolean_t had_revision = FALSE;
2359 /* One sub-pool for each revision and one for each txdelta chunk.
2360 Note that the rev_pool must live during the following txdelta. */
2361 rev_pool = svn_pool_create(pool);
2362 chunk_pool = svn_pool_create(pool);
2364 path = reparent_path(session, path, pool);
2365 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2367 include_merged_revisions));
2369 /* Servers before 1.1 don't support this command. Check for this here. */
2370 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2371 N_("'get-file-revs' not implemented")));
2375 svn_ra_svn__list_t *rev_proplist, *proplist;
2376 apr_uint64_t merged_rev_param;
2377 apr_array_header_t *props;
2378 svn_ra_svn__item_t *item;
2379 apr_hash_t *rev_props;
2382 svn_boolean_t merged_rev;
2383 svn_txdelta_window_handler_t d_handler;
2386 svn_pool_clear(rev_pool);
2387 svn_pool_clear(chunk_pool);
2388 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2389 if (is_done_response(item))
2391 /* Either we've got a correct revision or we will error out below. */
2392 had_revision = TRUE;
2393 if (item->kind != SVN_RA_SVN_LIST)
2394 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2395 _("Revision entry not a list"));
2397 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list,
2398 "crll?B", &p, &rev, &rev_proplist,
2399 &proplist, &merged_rev_param));
2400 p = svn_fspath__canonicalize(p, rev_pool);
2401 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2402 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2403 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2406 merged_rev = (svn_boolean_t) merged_rev_param;
2408 /* Get the first delta chunk so we know if there is a delta. */
2409 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2410 if (item->kind != SVN_RA_SVN_STRING)
2411 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2412 _("Text delta chunk not a string"));
2413 has_txdelta = item->u.string.len > 0;
2415 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2416 has_txdelta ? &d_handler : NULL, &d_baton,
2419 /* Process the text delta if any. */
2422 svn_stream_t *stream;
2424 if (d_handler && d_handler != svn_delta_noop_window_handler)
2425 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2429 while (item->u.string.len > 0)
2433 size = item->u.string.len;
2435 SVN_ERR(svn_stream_write(stream, item->u.string.data, &size));
2436 svn_pool_clear(chunk_pool);
2438 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2440 if (item->kind != SVN_RA_SVN_STRING)
2441 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2442 _("Text delta chunk not a string"));
2445 SVN_ERR(svn_stream_close(stream));
2449 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2451 /* Return error if we didn't get any revisions. */
2453 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2454 _("The get-file-revs command didn't return "
2457 svn_pool_destroy(chunk_pool);
2458 svn_pool_destroy(rev_pool);
2460 return SVN_NO_ERROR;
2463 /* For each path in PATH_REVS, send a 'lock' command to the server.
2464 Used with 1.2.x series servers which support locking, but of only
2465 one path at a time. ra_svn_lock(), which supports 'lock-many'
2466 is now the default. See svn_ra_lock() docstring for interface details. */
2467 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2468 apr_hash_t *path_revs,
2469 const char *comment,
2470 svn_boolean_t steal_lock,
2471 svn_ra_lock_callback_t lock_func,
2475 svn_ra_svn__session_baton_t *sess = session->priv;
2476 svn_ra_svn_conn_t* conn = sess->conn;
2477 svn_ra_svn__list_t *list;
2478 apr_hash_index_t *hi;
2479 apr_pool_t *iterpool = svn_pool_create(pool);
2481 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2487 svn_revnum_t *revnum;
2488 svn_error_t *err, *callback_err = NULL;
2490 svn_pool_clear(iterpool);
2492 apr_hash_this(hi, &key, NULL, &val);
2496 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2497 steal_lock, *revnum));
2499 /* Servers before 1.2 doesn't support locking. Check this here. */
2500 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2501 N_("Server doesn't support "
2502 "the lock command")));
2504 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2507 SVN_ERR(parse_lock(list, iterpool, &lock));
2509 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2513 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2516 svn_error_clear(err);
2519 return callback_err;
2522 svn_pool_destroy(iterpool);
2524 return SVN_NO_ERROR;
2527 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2528 Used with 1.2.x series servers which support unlocking, but of only
2529 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
2530 now the default. See svn_ra_unlock() docstring for interface details. */
2531 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2532 apr_hash_t *path_tokens,
2533 svn_boolean_t break_lock,
2534 svn_ra_lock_callback_t lock_func,
2538 svn_ra_svn__session_baton_t *sess = session->priv;
2539 svn_ra_svn_conn_t* conn = sess->conn;
2540 apr_hash_index_t *hi;
2541 apr_pool_t *iterpool = svn_pool_create(pool);
2543 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2548 const svn_string_t *token;
2549 svn_error_t *err, *callback_err = NULL;
2551 svn_pool_clear(iterpool);
2553 apr_hash_this(hi, &key, NULL, &val);
2555 if (strcmp(val, "") != 0)
2556 token = svn_string_create(val, iterpool);
2560 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2563 /* Servers before 1.2 don't support locking. Check this here. */
2564 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2565 N_("Server doesn't support the unlock "
2568 err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2570 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2574 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2576 svn_error_clear(err);
2579 return callback_err;
2582 svn_pool_destroy(iterpool);
2584 return SVN_NO_ERROR;
2587 /* Tell the server to lock all paths in PATH_REVS.
2588 See svn_ra_lock() for interface details. */
2589 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2590 apr_hash_t *path_revs,
2591 const char *comment,
2592 svn_boolean_t steal_lock,
2593 svn_ra_lock_callback_t lock_func,
2597 svn_ra_svn__session_baton_t *sess = session->priv;
2598 svn_ra_svn_conn_t *conn = sess->conn;
2599 apr_hash_index_t *hi;
2601 apr_pool_t *iterpool = svn_pool_create(pool);
2603 path_revs = reparent_path_hash(session, path_revs, pool, pool);
2604 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2605 comment, steal_lock));
2607 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2612 svn_revnum_t *revnum;
2614 svn_pool_clear(iterpool);
2615 apr_hash_this(hi, &key, NULL, &val);
2619 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2622 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2624 err = handle_auth_request(sess, pool);
2626 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2628 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2630 svn_error_clear(err);
2631 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2632 lock_func, lock_baton, pool);
2638 /* Loop over responses to get lock information. */
2639 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2641 svn_ra_svn__item_t *elt;
2644 svn_error_t *callback_err;
2647 svn_ra_svn__list_t *list;
2649 apr_hash_this(hi, &key, NULL, NULL);
2652 svn_pool_clear(iterpool);
2653 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2655 /* The server might have encountered some sort of fatal error in
2656 the middle of the request list. If this happens, it will
2657 transmit "done" to end the lock-info early, and then the
2658 overall command response will talk about the fatal error. */
2659 if (is_done_response(elt))
2662 if (elt->kind != SVN_RA_SVN_LIST)
2663 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2664 _("Lock response not a list"));
2666 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "wl", &status, &list));
2668 if (strcmp(status, "failure") == 0)
2669 err = svn_ra_svn__handle_failure_status(list);
2670 else if (strcmp(status, "success") == 0)
2672 SVN_ERR(parse_lock(list, iterpool, &lock));
2676 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2677 _("Unknown status for lock command"));
2680 callback_err = lock_func(lock_baton, path, TRUE,
2684 callback_err = SVN_NO_ERROR;
2686 svn_error_clear(err);
2689 return callback_err;
2692 /* If we didn't break early above, and the whole hash was traversed,
2693 read the final "done" from the server. */
2696 svn_ra_svn__item_t *elt;
2698 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2699 if (!is_done_response(elt))
2700 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2701 _("Didn't receive end marker for lock "
2705 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2707 svn_pool_destroy(iterpool);
2709 return SVN_NO_ERROR;
2712 /* Tell the server to unlock all paths in PATH_TOKENS.
2713 See svn_ra_unlock() for interface details. */
2714 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2715 apr_hash_t *path_tokens,
2716 svn_boolean_t break_lock,
2717 svn_ra_lock_callback_t lock_func,
2721 svn_ra_svn__session_baton_t *sess = session->priv;
2722 svn_ra_svn_conn_t *conn = sess->conn;
2723 apr_hash_index_t *hi;
2724 apr_pool_t *iterpool = svn_pool_create(pool);
2728 path_tokens = reparent_path_hash(session, path_tokens, pool, pool);
2729 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2732 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2738 svn_pool_clear(iterpool);
2739 apr_hash_this(hi, &key, NULL, &val);
2742 if (strcmp(val, "") != 0)
2747 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2750 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2752 err = handle_auth_request(sess, pool);
2754 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2757 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2759 svn_error_clear(err);
2760 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2767 /* Loop over responses to unlock files. */
2768 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2770 svn_ra_svn__item_t *elt;
2772 svn_error_t *callback_err;
2774 svn_ra_svn__list_t *list;
2776 svn_pool_clear(iterpool);
2778 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2780 /* The server might have encountered some sort of fatal error in
2781 the middle of the request list. If this happens, it will
2782 transmit "done" to end the lock-info early, and then the
2783 overall command response will talk about the fatal error. */
2784 if (is_done_response(elt))
2787 apr_hash_this(hi, &key, NULL, NULL);
2790 if (elt->kind != SVN_RA_SVN_LIST)
2791 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2792 _("Unlock response not a list"));
2794 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "wl", &status, &list));
2796 if (strcmp(status, "failure") == 0)
2797 err = svn_ra_svn__handle_failure_status(list);
2798 else if (strcmp(status, "success") == 0)
2800 SVN_ERR(svn_ra_svn__parse_tuple(list, "c", &path));
2804 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2805 _("Unknown status for unlock command"));
2808 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2811 callback_err = SVN_NO_ERROR;
2813 svn_error_clear(err);
2816 return callback_err;
2819 /* If we didn't break early above, and the whole hash was traversed,
2820 read the final "done" from the server. */
2823 svn_ra_svn__item_t *elt;
2825 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2826 if (! is_done_response(elt))
2827 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2828 _("Didn't receive end marker for unlock "
2832 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2834 svn_pool_destroy(iterpool);
2836 return SVN_NO_ERROR;
2839 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2844 svn_ra_svn__session_baton_t *sess = session->priv;
2845 svn_ra_svn_conn_t* conn = sess->conn;
2846 svn_ra_svn__list_t *list;
2848 path = reparent_path(session, path, pool);
2849 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2851 /* Servers before 1.2 doesn't support locking. Check this here. */
2852 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2853 N_("Server doesn't support the get-lock "
2856 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2858 SVN_ERR(parse_lock(list, pool, lock));
2862 return SVN_NO_ERROR;
2865 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2866 to prevent a dependency cycle. */
2867 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2868 const char **rel_path,
2872 const char *root_url;
2874 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2875 *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2877 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2878 _("'%s' isn't a child of repository root "
2881 return SVN_NO_ERROR;
2884 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2890 svn_ra_svn__session_baton_t *sess = session->priv;
2891 svn_ra_svn_conn_t* conn = sess->conn;
2892 svn_ra_svn__list_t *list;
2893 const char *full_url, *abs_path;
2896 /* Figure out the repository abspath from PATH. */
2897 full_url = svn_path_url_add_component2(sess->parent->client_url->data,
2899 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2900 abs_path = svn_fspath__canonicalize(abs_path, pool);
2902 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2904 /* Servers before 1.2 doesn't support locking. Check this here. */
2905 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2906 N_("Server doesn't support the get-lock "
2909 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2911 *locks = apr_hash_make(pool);
2913 for (i = 0; i < list->nelts; ++i)
2916 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(list, i);
2918 if (elt->kind != SVN_RA_SVN_LIST)
2919 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2920 _("Lock element not a list"));
2921 SVN_ERR(parse_lock(&elt->u.list, pool, &lock));
2923 /* Filter out unwanted paths. Since Subversion only allows
2924 locks on files, we can treat depth=immediates the same as
2925 depth=files for filtering purposes. Meaning, we'll keep
2928 a) its path is the very path we queried, or
2929 b) we've asked for a fully recursive answer, or
2930 c) we've asked for depth=files or depth=immediates, and this
2931 lock is on an immediate child of our query path.
2933 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2935 svn_hash_sets(*locks, lock->path, lock);
2937 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2939 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2940 if (relpath && (svn_path_component_count(relpath) == 1))
2941 svn_hash_sets(*locks, lock->path, lock);
2945 return SVN_NO_ERROR;
2949 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2950 svn_revnum_t revision,
2951 svn_revnum_t low_water_mark,
2952 svn_boolean_t send_deltas,
2953 const svn_delta_editor_t *editor,
2957 svn_ra_svn__session_baton_t *sess = session->priv;
2959 /* Complex EDITOR callbacks may rely on client and server parent path
2961 SVN_ERR(ensure_exact_server_parent(session, pool));
2962 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2963 low_water_mark, send_deltas));
2965 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2966 N_("Server doesn't support the replay "
2969 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2972 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2976 static svn_error_t *
2977 ra_svn_replay_range(svn_ra_session_t *session,
2978 svn_revnum_t start_revision,
2979 svn_revnum_t end_revision,
2980 svn_revnum_t low_water_mark,
2981 svn_boolean_t send_deltas,
2982 svn_ra_replay_revstart_callback_t revstart_func,
2983 svn_ra_replay_revfinish_callback_t revfinish_func,
2987 svn_ra_svn__session_baton_t *sess = session->priv;
2988 apr_pool_t *iterpool;
2990 svn_boolean_t drive_aborted = FALSE;
2992 /* Complex EDITOR callbacks may rely on client and server parent path
2994 SVN_ERR(ensure_exact_server_parent(session, pool));
2995 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2996 start_revision, end_revision,
2997 low_water_mark, send_deltas));
2999 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
3000 N_("Server doesn't support the "
3001 "replay-range command")));
3003 iterpool = svn_pool_create(pool);
3004 for (rev = start_revision; rev <= end_revision; rev++)
3006 const svn_delta_editor_t *editor;
3008 apr_hash_t *rev_props;
3010 svn_ra_svn__list_t *list;
3012 svn_pool_clear(iterpool);
3014 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
3015 "wl", &word, &list));
3017 if (strcmp(word, "revprops") != 0)
3019 if (strcmp(word, "failure") == 0)
3020 SVN_ERR(svn_ra_svn__handle_failure_status(list));
3022 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3023 _("Expected 'revprops', found '%s'"),
3027 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
3029 SVN_ERR(revstart_func(rev, replay_baton,
3030 &editor, &edit_baton,
3033 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
3035 &drive_aborted, TRUE));
3036 /* If drive_editor2() aborted the commit, do NOT try to call
3037 revfinish_func and commit the transaction! */
3038 if (drive_aborted) {
3039 svn_pool_destroy(iterpool);
3040 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
3041 _("Error while replaying commit"));
3043 SVN_ERR(revfinish_func(rev, replay_baton,
3048 svn_pool_destroy(iterpool);
3050 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
3054 static svn_error_t *
3055 ra_svn_has_capability(svn_ra_session_t *session,
3057 const char *capability,
3060 svn_ra_svn__session_baton_t *sess = session->priv;
3061 static const char* capabilities[][2] =
3063 /* { ra capability string, svn:// wire capability string} */
3064 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
3065 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
3066 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
3067 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
3068 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
3069 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
3070 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
3071 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
3072 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
3073 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
3074 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
3075 {SVN_RA_CAPABILITY_LIST, SVN_RA_SVN_CAP_LIST},
3077 {NULL, NULL} /* End of list marker */
3083 for (i = 0; capabilities[i][0]; i++)
3085 if (strcmp(capability, capabilities[i][0]) == 0)
3087 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
3088 return SVN_NO_ERROR;
3092 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
3093 _("Don't know anything about capability '%s'"),
3097 static svn_error_t *
3098 ra_svn_get_deleted_rev(svn_ra_session_t *session,
3100 svn_revnum_t peg_revision,
3101 svn_revnum_t end_revision,
3102 svn_revnum_t *revision_deleted,
3106 svn_ra_svn__session_baton_t *sess_baton = session->priv;
3107 svn_ra_svn_conn_t *conn = sess_baton->conn;
3109 path = reparent_path(session, path, pool);
3111 /* Transmit the parameters. */
3112 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
3113 peg_revision, end_revision));
3115 /* Servers before 1.6 don't support this command. Check for this here. */
3116 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
3117 N_("'get-deleted-rev' not implemented")));
3119 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
3123 static svn_error_t *
3124 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
3125 svn_delta_shim_callbacks_t *callbacks)
3127 svn_ra_svn__session_baton_t *sess_baton = session->priv;
3128 svn_ra_svn_conn_t *conn = sess_baton->conn;
3130 conn->shim_callbacks = callbacks;
3132 return SVN_NO_ERROR;
3135 static svn_error_t *
3136 ra_svn_get_inherited_props(svn_ra_session_t *session,
3137 apr_array_header_t **iprops,
3139 svn_revnum_t revision,
3140 apr_pool_t *result_pool,
3141 apr_pool_t *scratch_pool)
3143 svn_ra_svn__session_baton_t *sess_baton = session->priv;
3144 svn_ra_svn_conn_t *conn = sess_baton->conn;
3145 svn_ra_svn__list_t *iproplist;
3146 svn_boolean_t iprop_capable;
3148 path = reparent_path(session, path, scratch_pool);
3149 SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
3150 SVN_RA_CAPABILITY_INHERITED_PROPS,
3153 /* If we don't support native iprop handling, use the implementation
3156 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
3158 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
3160 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
3161 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
3162 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
3165 return SVN_NO_ERROR;
3168 static svn_error_t *
3169 ra_svn_list(svn_ra_session_t *session,
3171 svn_revnum_t revision,
3172 const apr_array_header_t *patterns,
3174 apr_uint32_t dirent_fields,
3175 svn_ra_dirent_receiver_t receiver,
3176 void *receiver_baton,
3177 apr_pool_t *scratch_pool)
3179 svn_ra_svn__session_baton_t *sess_baton = session->priv;
3180 svn_ra_svn_conn_t *conn = sess_baton->conn;
3182 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3184 path = reparent_path(session, path, scratch_pool);
3186 /* Send the list request. */
3187 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(c(?r)w(!", "list",
3188 path, revision, svn_depth_to_word(depth)));
3189 SVN_ERR(send_dirent_fields(conn, dirent_fields, scratch_pool));
3193 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!)(!"));
3195 for (i = 0; i < patterns->nelts; ++i)
3197 const char *pattern = APR_ARRAY_IDX(patterns, i, const char *);
3198 SVN_ERR(svn_ra_svn__write_cstring(conn, scratch_pool, pattern));
3202 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))"));
3204 /* Handle auth request by server */
3205 SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
3207 /* Read and process list response. */
3210 svn_ra_svn__item_t *item;
3211 const char *dirent_path;
3212 const char *kind_word, *date;
3213 svn_dirent_t dirent = { 0 };
3215 svn_pool_clear(iterpool);
3217 /* Read the next dirent or bail out on "done", respectively */
3218 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
3219 if (is_done_response(item))
3221 if (item->kind != SVN_RA_SVN_LIST)
3222 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3223 _("List entry not a list"));
3224 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list,
3225 "cw?(?n)(?b)(?r)(?c)(?c)",
3226 &dirent_path, &kind_word, &dirent.size,
3227 &dirent.has_props, &dirent.created_rev,
3228 &date, &dirent.last_author));
3231 dirent.kind = svn_node_kind_from_word(kind_word);
3233 SVN_ERR(svn_time_from_cstring(&dirent.time, date, iterpool));
3235 /* Invoke RECEIVER */
3236 SVN_ERR(receiver(dirent_path, &dirent, receiver_baton, iterpool));
3238 svn_pool_destroy(iterpool);
3240 /* Read the actual command response. */
3241 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, ""));
3242 return SVN_NO_ERROR;
3245 static const svn_ra__vtable_t ra_svn_vtable = {
3247 ra_svn_get_description,
3252 ra_svn_get_session_url,
3253 ra_svn_get_latest_rev,
3254 ra_svn_get_dated_rev,
3255 ra_svn_change_rev_prop,
3256 ra_svn_rev_proplist,
3261 ra_svn_get_mergeinfo,
3270 ra_svn_get_repos_root,
3271 ra_svn_get_locations,
3272 ra_svn_get_location_segments,
3273 ra_svn_get_file_revs,
3279 ra_svn_has_capability,
3280 ra_svn_replay_range,
3281 ra_svn_get_deleted_rev,
3282 ra_svn_get_inherited_props,
3283 NULL /* ra_set_svn_ra_open */,
3285 ra_svn_register_editor_shim_callbacks,
3286 NULL /* commit_ev2 */,
3287 NULL /* replay_range_ev2 */
3291 svn_ra_svn__init(const svn_version_t *loader_version,
3292 const svn_ra__vtable_t **vtable,
3295 static const svn_version_checklist_t checklist[] =
3297 { "svn_subr", svn_subr_version },
3298 { "svn_delta", svn_delta_version },
3302 SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
3304 /* Simplified version check to make sure we can safely use the
3305 VTABLE parameter. The RA loader does a more exhaustive check. */
3306 if (loader_version->major != SVN_VER_MAJOR)
3308 return svn_error_createf
3309 (SVN_ERR_VERSION_MISMATCH, NULL,
3310 _("Unsupported RA loader version (%d) for ra_svn"),
3311 loader_version->major);
3314 *vtable = &ra_svn_vtable;
3316 #ifdef SVN_HAVE_SASL
3317 SVN_ERR(svn_ra_svn__sasl_init());
3320 return SVN_NO_ERROR;
3323 /* Compatibility wrapper for the 1.1 and before API. */
3324 #define NAME "ra_svn"
3325 #define DESCRIPTION RA_SVN_DESCRIPTION
3326 #define VTBL ra_svn_vtable
3327 #define INITFUNC svn_ra_svn__init
3328 #define COMPAT_INITFUNC svn_ra_svn_init
3329 #include "../libsvn_ra/wrapper_template.h"