In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_svn / client.c
bloba92763fe3738b6774e07ef432ff6876c9dad5a5c
1 /*
2 * client.c : Functions for repository access via the Subversion protocol
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include "svn_private_config.h"
23 #define APR_WANT_STRFUNC
24 #include <apr_want.h>
25 #include <apr_general.h>
26 #include <apr_strings.h>
27 #include <apr_network_io.h>
28 #include <apr_md5.h>
29 #include <apr_uri.h>
30 #include <assert.h>
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_error.h"
35 #include "svn_time.h"
36 #include "svn_path.h"
37 #include "svn_pools.h"
38 #include "svn_config.h"
39 #include "svn_private_config.h"
40 #include "svn_ra.h"
41 #include "../libsvn_ra/ra_loader.h"
42 #include "svn_ra_svn.h"
43 #include "svn_md5.h"
44 #include "svn_props.h"
45 #include "svn_mergeinfo.h"
47 #include "ra_svn.h"
49 #ifdef SVN_HAVE_SASL
50 #define DO_AUTH svn_ra_svn__do_cyrus_auth
51 #else
52 #define DO_AUTH svn_ra_svn__do_internal_auth
53 #endif
55 /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
56 whatever reason) deems svn_depth_immediates as non-recursive, which
57 is ... kinda true, but not true enough for our purposes. We need
58 our requested recursion level to be *at least* as recursive as the
59 real depth we're looking for.
61 #define DEPTH_TO_RECURSE(d) \
62 (((d) == svn_depth_unknown || (d) > svn_depth_files) ? TRUE : FALSE)
64 typedef struct {
65 svn_ra_svn__session_baton_t *sess_baton;
66 apr_pool_t *pool;
67 svn_revnum_t *new_rev;
68 svn_commit_callback2_t callback;
69 void *callback_baton;
70 } ra_svn_commit_callback_baton_t;
72 typedef struct {
73 svn_ra_svn__session_baton_t *sess_baton;
74 svn_ra_svn_conn_t *conn;
75 apr_pool_t *pool;
76 const svn_delta_editor_t *editor;
77 void *edit_baton;
78 } ra_svn_reporter_baton_t;
80 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
81 portion. */
82 static void parse_tunnel(const char *url, const char **tunnel,
83 apr_pool_t *pool)
85 const char *p;
87 *tunnel = NULL;
89 if (strncasecmp(url, "svn", 3) != 0)
90 return;
91 url += 3;
93 /* Get the tunnel specification, if any. */
94 if (*url == '+')
96 url++;
97 p = strchr(url, ':');
98 if (!p)
99 return;
100 *tunnel = apr_pstrmemdup(pool, url, p - url);
101 url = p;
105 static svn_error_t *make_connection(const char *hostname, unsigned short port,
106 apr_socket_t **sock, apr_pool_t *pool)
108 apr_sockaddr_t *sa;
109 apr_status_t status;
110 int family = APR_INET;
112 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
113 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
114 create IPV6 sockets. */
116 #if APR_HAVE_IPV6
117 #ifdef MAX_SECS_TO_LINGER
118 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
119 #else
120 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
121 APR_PROTO_TCP, pool);
122 #endif
123 if (status == 0)
125 apr_socket_close(*sock);
126 family = APR_UNSPEC;
128 #endif
130 /* Resolve the hostname. */
131 status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
132 if (status)
133 return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
134 hostname);
136 /* Create the socket. */
137 #ifdef MAX_SECS_TO_LINGER
138 /* ### old APR interface */
139 status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
140 #else
141 status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
142 pool);
143 #endif
144 if (status)
145 return svn_error_wrap_apr(status, _("Can't create socket"));
147 status = apr_socket_connect(*sock, sa);
148 if (status)
149 return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
150 hostname);
152 return SVN_NO_ERROR;
155 /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
156 property diffs in LIST, received from the server. */
157 static svn_error_t *parse_prop_diffs(apr_array_header_t *list,
158 apr_pool_t *pool,
159 apr_array_header_t **diffs)
161 svn_ra_svn_item_t *elt;
162 svn_prop_t *prop;
163 int i;
165 *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
167 for (i = 0; i < list->nelts; i++)
169 elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
170 if (elt->kind != SVN_RA_SVN_LIST)
171 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
172 _("Prop diffs element not a list"));
173 prop = apr_array_push(*diffs);
174 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
175 &prop->value));
177 return SVN_NO_ERROR;
180 /* Parse a lockdesc, provided in LIST as specified by the protocol into
181 LOCK, allocated in POOL. */
182 static svn_error_t *parse_lock(apr_array_header_t *list, apr_pool_t *pool,
183 svn_lock_t **lock)
185 const char *cdate, *edate;
186 *lock = svn_lock_create(pool);
187 SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
188 &(*lock)->token, &(*lock)->owner,
189 &(*lock)->comment, &cdate, &edate));
190 (*lock)->path = svn_path_canonicalize((*lock)->path, pool);
191 SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
192 if (edate)
193 SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
194 return SVN_NO_ERROR;
197 static svn_error_t *interpret_kind(const char *str, apr_pool_t *pool,
198 svn_node_kind_t *kind)
200 if (strcmp(str, "none") == 0)
201 *kind = svn_node_none;
202 else if (strcmp(str, "file") == 0)
203 *kind = svn_node_file;
204 else if (strcmp(str, "dir") == 0)
205 *kind = svn_node_dir;
206 else if (strcmp(str, "unknown") == 0)
207 *kind = svn_node_unknown;
208 else
209 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
210 _("Unrecognized node kind '%s' from server"),
211 str);
212 return SVN_NO_ERROR;
215 /* --- AUTHENTICATION ROUTINES --- */
217 svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
218 apr_pool_t *pool,
219 const char *mech, const char *mech_arg)
221 return svn_ra_svn_write_tuple(conn, pool, "w(?c)", mech, mech_arg);
224 static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
225 apr_pool_t *pool)
227 svn_ra_svn_conn_t *conn = sess->conn;
228 apr_array_header_t *mechlist;
229 const char *realm;
231 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "lc", &mechlist, &realm));
232 if (mechlist->nelts == 0)
233 return SVN_NO_ERROR;
234 return DO_AUTH(sess, mechlist, realm, pool);
237 /* --- REPORTER IMPLEMENTATION --- */
239 static svn_error_t *ra_svn_set_path(void *baton, const char *path,
240 svn_revnum_t rev,
241 svn_depth_t depth,
242 svn_boolean_t start_empty,
243 const char *lock_token,
244 apr_pool_t *pool)
246 ra_svn_reporter_baton_t *b = baton;
248 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "set-path", "crb(?c)w",
249 path, rev, start_empty, lock_token,
250 svn_depth_to_word(depth)));
251 return SVN_NO_ERROR;
254 static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
255 apr_pool_t *pool)
257 ra_svn_reporter_baton_t *b = baton;
259 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-path", "c", path));
260 return SVN_NO_ERROR;
263 static svn_error_t *ra_svn_link_path(void *baton, const char *path,
264 const char *url,
265 svn_revnum_t rev,
266 svn_depth_t depth,
267 svn_boolean_t start_empty,
268 const char *lock_token,
269 apr_pool_t *pool)
271 ra_svn_reporter_baton_t *b = baton;
273 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "link-path", "ccrb(?c)w",
274 path, url, rev, start_empty, lock_token,
275 svn_depth_to_word(depth)));
276 return SVN_NO_ERROR;
279 static svn_error_t *ra_svn_finish_report(void *baton,
280 apr_pool_t *pool)
282 ra_svn_reporter_baton_t *b = baton;
284 SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "finish-report", ""));
285 SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
286 SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
287 NULL, FALSE));
288 SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, b->pool, ""));
289 return SVN_NO_ERROR;
292 static svn_error_t *ra_svn_abort_report(void *baton,
293 apr_pool_t *pool)
295 ra_svn_reporter_baton_t *b = baton;
297 SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "abort-report", ""));
298 return SVN_NO_ERROR;
301 static svn_ra_reporter3_t ra_svn_reporter = {
302 ra_svn_set_path,
303 ra_svn_delete_path,
304 ra_svn_link_path,
305 ra_svn_finish_report,
306 ra_svn_abort_report
309 static void ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
310 apr_pool_t *pool,
311 const svn_delta_editor_t *editor,
312 void *edit_baton,
313 const char *target,
314 svn_depth_t depth,
315 const svn_ra_reporter3_t **reporter,
316 void **report_baton)
318 ra_svn_reporter_baton_t *b;
319 const svn_delta_editor_t *filter_editor;
320 void *filter_baton;
322 /* We can skip the depth filtering when the user requested
323 depth_files or depth_infinity because the server will
324 transmit the right stuff anyway. */
325 if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
326 && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
328 svn_error_clear(svn_delta_depth_filter_editor(&filter_editor,
329 &filter_baton,
330 editor, edit_baton, depth,
331 *target ? TRUE : FALSE,
332 pool));
333 editor = filter_editor;
334 edit_baton = filter_baton;
337 b = apr_palloc(pool, sizeof(*b));
338 b->sess_baton = sess_baton;
339 b->conn = sess_baton->conn;
340 b->pool = pool;
341 b->editor = editor;
342 b->edit_baton = edit_baton;
344 *reporter = &ra_svn_reporter;
345 *report_baton = b;
348 /* --- RA LAYER IMPLEMENTATION --- */
350 /* (Note: *ARGV is an output parameter.) */
351 static svn_error_t *find_tunnel_agent(const char *tunnel,
352 const char *hostinfo,
353 const char ***argv,
354 apr_hash_t *config, apr_pool_t *pool)
356 svn_config_t *cfg;
357 const char *val, *var, *cmd;
358 char **cmd_argv;
359 apr_size_t len;
360 apr_status_t status;
361 int n;
363 /* Look up the tunnel specification in config. */
364 cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
365 APR_HASH_KEY_STRING) : NULL;
366 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
368 /* We have one predefined tunnel scheme, if it isn't overridden by config. */
369 if (!val && strcmp(tunnel, "ssh") == 0)
370 val = "$SVN_SSH ssh";
372 if (!val || !*val)
373 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
374 _("Undefined tunnel scheme '%s'"), tunnel);
376 /* If the scheme definition begins with "$varname", it means there
377 * is an environment variable which can override the command. */
378 if (*val == '$')
380 val++;
381 len = strcspn(val, " ");
382 var = apr_pstrmemdup(pool, val, len);
383 cmd = getenv(var);
384 if (!cmd)
386 cmd = val + len;
387 while (*cmd == ' ')
388 cmd++;
389 if (!*cmd)
390 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
391 _("Tunnel scheme %s requires environment "
392 "variable %s to be defined"), tunnel,
393 var);
396 else
397 cmd = val;
399 /* Tokenize the command into a list of arguments. */
400 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
401 if (status != APR_SUCCESS)
402 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
404 /* Append the fixed arguments to the result. */
405 for (n = 0; cmd_argv[n] != NULL; n++)
407 *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
408 memcpy((void *) *argv, cmd_argv, n * sizeof(char *));
409 (*argv)[n++] = svn_path_uri_decode(hostinfo, pool);
410 (*argv)[n++] = "svnserve";
411 (*argv)[n++] = "-t";
412 (*argv)[n] = NULL;
414 return SVN_NO_ERROR;
417 /* This function handles any errors which occur in the child process
418 * created for a tunnel agent. We write the error out as a command
419 * failure; the code in ra_svn_open() to read the server's greeting
420 * will see the error and return it to the caller. */
421 static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
422 const char *desc)
424 svn_ra_svn_conn_t *conn;
425 apr_file_t *in_file, *out_file;
426 svn_error_t *err;
428 if (apr_file_open_stdin(&in_file, pool)
429 || apr_file_open_stdout(&out_file, pool))
430 return;
432 conn = svn_ra_svn_create_conn(NULL, in_file, out_file, pool);
433 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
434 svn_error_clear(svn_ra_svn_write_cmd_failure(conn, pool, err));
435 svn_error_clear(svn_ra_svn_flush(conn, pool));
438 /* (Note: *CONN is an output parameter.) */
439 static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
440 apr_pool_t *pool)
442 apr_status_t status;
443 apr_proc_t *proc;
444 apr_procattr_t *attr;
446 status = apr_procattr_create(&attr, pool);
447 if (status == APR_SUCCESS)
448 status = apr_procattr_io_set(attr, 1, 1, 0);
449 if (status == APR_SUCCESS)
450 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
451 if (status == APR_SUCCESS)
452 status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
453 proc = apr_palloc(pool, sizeof(*proc));
454 if (status == APR_SUCCESS)
455 status = apr_proc_create(proc, *args, args, NULL, attr, pool);
456 if (status != APR_SUCCESS)
457 return svn_error_wrap_apr(status, _("Can't create tunnel"));
459 /* Arrange for the tunnel agent to get a SIGKILL on pool
460 * cleanup. This is a little extreme, but the alternatives
461 * weren't working out:
462 * - Closing the pipes and waiting for the process to die
463 * was prone to mysterious hangs which are difficult to
464 * diagnose (e.g. svnserve dumps core due to unrelated bug;
465 * sshd goes into zombie state; ssh connection is never
466 * closed; ssh never terminates).
467 * - Killing the tunnel agent with SIGTERM leads to unsightly
468 * stderr output from ssh.
470 apr_pool_note_subprocess(pool, proc, APR_KILL_ALWAYS);
472 /* APR pipe objects inherit by default. But we don't want the
473 * tunnel agent's pipes held open by future child processes
474 * (such as other ra_svn sessions), so turn that off. */
475 apr_file_inherit_unset(proc->in);
476 apr_file_inherit_unset(proc->out);
478 /* Guard against dotfile output to stdout on the server. */
479 *conn = svn_ra_svn_create_conn(NULL, proc->out, proc->in, pool);
480 SVN_ERR(svn_ra_svn_skip_leading_garbage(*conn, pool));
481 return SVN_NO_ERROR;
484 /* Parse URL inot URI, validating it and setting the default port if none
485 was given. Allocate the URI fileds out of POOL. */
486 static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
487 apr_pool_t *pool)
489 apr_status_t apr_err;
491 apr_err = apr_uri_parse(pool, url, uri);
493 if (apr_err != 0)
494 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
495 _("Illegal svn repository URL '%s'"), url);
497 if (! uri->port)
498 uri->port = SVN_RA_SVN_PORT;
500 return SVN_NO_ERROR;
503 /* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
504 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
505 are provided by the caller of ra_svn_open. If tunnel_argv is non-null,
506 it points to a program argument list to use when invoking the tunnel agent.
508 static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
509 const char *url,
510 const apr_uri_t *uri,
511 const char **tunnel_argv,
512 const svn_ra_callbacks2_t *callbacks,
513 void *callbacks_baton,
514 apr_pool_t *pool)
516 svn_ra_svn__session_baton_t *sess;
517 svn_ra_svn_conn_t *conn;
518 apr_socket_t *sock;
519 apr_uint64_t minver, maxver;
520 apr_array_header_t *mechlist, *caplist;
522 sess = apr_palloc(pool, sizeof(*sess));
523 sess->pool = pool;
524 sess->is_tunneled = (tunnel_argv != NULL);
525 sess->url = apr_pstrdup(pool, url);
526 sess->user = uri->user;
527 sess->hostname = uri->hostname;
528 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
529 uri->port);
530 sess->tunnel_argv = tunnel_argv;
531 sess->callbacks = callbacks;
532 sess->callbacks_baton = callbacks_baton;
533 sess->bytes_read = sess->bytes_written = 0;
535 if (tunnel_argv)
536 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
537 else
539 SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
540 conn = svn_ra_svn_create_conn(sock, NULL, NULL, pool);
543 /* Make sure we set conn->session before reading from it,
544 * because the reader and writer functions expect a non-NULL value. */
545 sess->conn = conn;
546 conn->session = sess;
548 /* Read server's greeting. */
549 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "nnll", &minver, &maxver,
550 &mechlist, &caplist));
551 /* We support protocol version 2. */
552 if (minver > 2)
553 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
554 _("Server requires minimum version %d"),
555 (int) minver);
556 if (maxver < 2)
557 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
558 _("Server only supports versions up to %d"),
559 (int) maxver);
560 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
562 /* All released versions of Subversion support edit-pipeline,
563 * so we do not support servers that do not. */
564 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
565 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
566 _("Server does not support edit pipelining"));
568 /* In protocol version 2, we send back our protocol version, our
569 * capability list, and the URL, and subsequently there is an auth
570 * request. */
571 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(wwwwww)c", (apr_uint64_t) 2,
572 SVN_RA_SVN_CAP_EDIT_PIPELINE,
573 SVN_RA_SVN_CAP_SVNDIFF1,
574 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
575 SVN_RA_SVN_CAP_DEPTH,
576 SVN_RA_SVN_CAP_MERGEINFO,
577 SVN_RA_SVN_CAP_LOG_REVPROPS,
578 url));
579 SVN_ERR(handle_auth_request(sess, pool));
581 /* This is where the security layer would go into effect if we
582 * supported security layers, which is a ways off. */
584 /* Read the repository's uuid and root URL. */
585 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "c?c", &conn->uuid,
586 &conn->repos_root));
587 if (conn->repos_root)
589 conn->repos_root = svn_path_canonicalize(conn->repos_root, pool);
590 /* We should check that the returned string is a prefix of url, since
591 that's the API guarantee, but this isn't true for 1.0 servers.
592 Checking the length prevents client crashes. */
593 if (strlen(conn->repos_root) > strlen(url))
594 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
595 _("Impossibly long repository root from "
596 "server"));
599 *sess_p = sess;
601 return SVN_NO_ERROR;
605 #define RA_SVN_DESCRIPTION \
606 N_("Module for accessing a repository using the svn network protocol.")
608 static const char *ra_svn_get_description(void)
610 return _(RA_SVN_DESCRIPTION);
613 static const char * const *
614 ra_svn_get_schemes(apr_pool_t *pool)
616 static const char *schemes[] = { "svn", NULL };
618 return schemes;
623 static svn_error_t *ra_svn_open(svn_ra_session_t *session, const char *url,
624 const svn_ra_callbacks2_t *callbacks,
625 void *callback_baton,
626 apr_hash_t *config,
627 apr_pool_t *pool)
629 apr_pool_t *sess_pool = svn_pool_create(pool);
630 svn_ra_svn__session_baton_t *sess;
631 const char *tunnel, **tunnel_argv;
632 apr_uri_t uri;
634 SVN_ERR(parse_url(url, &uri, sess_pool));
636 parse_tunnel(url, &tunnel, pool);
638 if (tunnel)
639 SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
640 pool));
641 else
642 tunnel_argv = NULL;
644 /* We open the session in a subpool so we can get rid of it if we
645 reparent with a server that doesn't support reparenting. */
646 SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
647 callbacks, callback_baton, sess_pool));
648 session->priv = sess;
650 return SVN_NO_ERROR;
653 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
654 const char *url,
655 apr_pool_t *pool)
657 svn_ra_svn__session_baton_t *sess = ra_session->priv;
658 svn_ra_svn_conn_t *conn = sess->conn;
659 svn_error_t *err;
660 apr_pool_t *sess_pool;
661 svn_ra_svn__session_baton_t *new_sess;
662 apr_uri_t uri;
664 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "reparent", "c", url));
665 err = handle_auth_request(sess, pool);
666 if (! err)
668 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
669 sess->url = apr_pstrdup(sess->pool, url);
670 return SVN_NO_ERROR;
672 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
673 return err;
675 /* Servers before 1.4 doesn't support this command; try to reconnect
676 instead. */
677 svn_error_clear(err);
678 /* Create a new subpool of the RA session pool. */
679 sess_pool = svn_pool_create(ra_session->pool);
680 err = parse_url(url, &uri, sess_pool);
681 if (! err)
682 err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
683 sess->callbacks, sess->callbacks_baton, sess_pool);
684 /* We destroy the new session pool on error, since it is allocated in
685 the main session pool. */
686 if (err)
688 svn_pool_destroy(sess_pool);
689 return err;
692 /* We have a new connection, assign it and destroy the old. */
693 ra_session->priv = new_sess;
694 svn_pool_destroy(sess->pool);
696 return SVN_NO_ERROR;
699 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
700 const char **url, apr_pool_t *pool)
702 svn_ra_svn__session_baton_t *sess = session->priv;
703 *url = apr_pstrdup(pool, sess->url);
704 return SVN_NO_ERROR;
707 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
708 svn_revnum_t *rev, apr_pool_t *pool)
710 svn_ra_svn__session_baton_t *sess_baton = session->priv;
711 svn_ra_svn_conn_t *conn = sess_baton->conn;
713 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-latest-rev", ""));
714 SVN_ERR(handle_auth_request(sess_baton, pool));
715 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
716 return SVN_NO_ERROR;
719 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
720 svn_revnum_t *rev, apr_time_t tm,
721 apr_pool_t *pool)
723 svn_ra_svn__session_baton_t *sess_baton = session->priv;
724 svn_ra_svn_conn_t *conn = sess_baton->conn;
726 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-dated-rev", "c",
727 svn_time_to_cstring(tm, pool)));
728 SVN_ERR(handle_auth_request(sess_baton, pool));
729 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
730 return SVN_NO_ERROR;
733 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
734 const char *name,
735 const svn_string_t *value,
736 apr_pool_t *pool)
738 svn_ra_svn__session_baton_t *sess_baton = session->priv;
739 svn_ra_svn_conn_t *conn = sess_baton->conn;
741 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop", "rc?s",
742 rev, name, value));
743 SVN_ERR(handle_auth_request(sess_baton, pool));
744 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
745 return SVN_NO_ERROR;
748 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
749 apr_pool_t *pool)
751 svn_ra_svn__session_baton_t *sess_baton = session->priv;
752 svn_ra_svn_conn_t *conn = sess_baton->conn;
754 *uuid = conn->uuid;
755 return SVN_NO_ERROR;
758 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
759 apr_pool_t *pool)
761 svn_ra_svn__session_baton_t *sess_baton = session->priv;
762 svn_ra_svn_conn_t *conn = sess_baton->conn;
764 if (!conn->repos_root)
765 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
766 _("Server did not send repository root"));
767 *url = conn->repos_root;
768 return SVN_NO_ERROR;
771 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
772 apr_hash_t **props, apr_pool_t *pool)
774 svn_ra_svn__session_baton_t *sess_baton = session->priv;
775 svn_ra_svn_conn_t *conn = sess_baton->conn;
776 apr_array_header_t *proplist;
778 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-proplist", "r", rev));
779 SVN_ERR(handle_auth_request(sess_baton, pool));
780 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &proplist));
781 SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
782 return SVN_NO_ERROR;
785 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
786 const char *name,
787 svn_string_t **value, apr_pool_t *pool)
789 svn_ra_svn__session_baton_t *sess_baton = session->priv;
790 svn_ra_svn_conn_t *conn = sess_baton->conn;
792 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-prop", "rc", rev, name));
793 SVN_ERR(handle_auth_request(sess_baton, pool));
794 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?s)", value));
795 return SVN_NO_ERROR;
798 static svn_error_t *ra_svn_end_commit(void *baton)
800 ra_svn_commit_callback_baton_t *ccb = baton;
801 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
803 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
804 SVN_ERR(svn_ra_svn_read_tuple(ccb->sess_baton->conn, ccb->pool,
805 "r(?c)(?c)?(?c)",
806 &(commit_info->revision),
807 &(commit_info->date),
808 &(commit_info->author),
809 &(commit_info->post_commit_err)));
811 return ccb->callback(commit_info, ccb->callback_baton, ccb->pool);
815 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
816 const svn_delta_editor_t **editor,
817 void **edit_baton,
818 apr_hash_t *revprop_table,
819 svn_commit_callback2_t callback,
820 void *callback_baton,
821 apr_hash_t *lock_tokens,
822 svn_boolean_t keep_locks,
823 apr_pool_t *pool)
825 svn_ra_svn__session_baton_t *sess_baton = session->priv;
826 svn_ra_svn_conn_t *conn = sess_baton->conn;
827 ra_svn_commit_callback_baton_t *ccb;
828 apr_hash_index_t *hi;
829 apr_pool_t *iterpool;
830 const svn_string_t *log_msg = apr_hash_get(revprop_table,
831 SVN_PROP_REVISION_LOG,
832 APR_HASH_KEY_STRING);
834 /* If we're sending revprops other than svn:log, make sure the server won't
835 silently ignore them. */
836 if (apr_hash_count(revprop_table) > 1 &&
837 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
838 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
839 _("Server doesn't support setting arbitrary "
840 "revision properties during commit"));
842 /* Tell the server we're starting the commit.
843 Send log message here for backwards compatibility with servers
844 before 1.5. */
845 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(!", "commit",
846 log_msg->data));
847 if (lock_tokens)
849 iterpool = svn_pool_create(pool);
850 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
852 const void *key;
853 void *val;
854 const char *path, *token;
855 svn_pool_clear(iterpool);
856 apr_hash_this(hi, &key, NULL, &val);
857 path = key;
858 token = val;
859 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cc", path, token));
861 svn_pool_destroy(iterpool);
863 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b(!", keep_locks));
864 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, revprop_table));
865 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
866 SVN_ERR(handle_auth_request(sess_baton, pool));
867 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
869 /* Remember a few arguments for when the commit is over. */
870 ccb = apr_palloc(pool, sizeof(*ccb));
871 ccb->sess_baton = sess_baton;
872 ccb->pool = pool;
873 ccb->callback = callback;
874 ccb->callback_baton = callback_baton;
876 /* Fetch an editor for the caller to drive. The editor will call
877 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
878 * in the new_rev, committed_date, and committed_author values. */
879 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
880 ra_svn_end_commit, ccb);
881 return SVN_NO_ERROR;
884 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
885 svn_revnum_t rev, svn_stream_t *stream,
886 svn_revnum_t *fetched_rev,
887 apr_hash_t **props,
888 apr_pool_t *pool)
890 svn_ra_svn__session_baton_t *sess_baton = session->priv;
891 svn_ra_svn_conn_t *conn = sess_baton->conn;
892 apr_array_header_t *proplist;
893 unsigned char digest[APR_MD5_DIGESTSIZE];
894 const char *expected_checksum, *hex_digest;
895 apr_md5_ctx_t md5_context;
896 apr_pool_t *iterpool;
898 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-file", "c(?r)bb", path,
899 rev, (props != NULL), (stream != NULL)));
900 SVN_ERR(handle_auth_request(sess_baton, pool));
901 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?c)rl",
902 &expected_checksum,
903 &rev, &proplist));
905 if (fetched_rev)
906 *fetched_rev = rev;
907 if (props)
908 SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
910 /* We're done if the contents weren't wanted. */
911 if (!stream)
912 return SVN_NO_ERROR;
914 if (expected_checksum)
915 apr_md5_init(&md5_context);
917 /* Read the file's contents. */
918 iterpool = svn_pool_create(pool);
919 while (1)
921 svn_ra_svn_item_t *item;
923 svn_pool_clear(iterpool);
924 SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &item));
925 if (item->kind != SVN_RA_SVN_STRING)
926 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
927 _("Non-string as part of file contents"));
928 if (item->u.string->len == 0)
929 break;
931 if (expected_checksum)
932 apr_md5_update(&md5_context, item->u.string->data,
933 item->u.string->len);
935 SVN_ERR(svn_stream_write(stream, item->u.string->data,
936 &item->u.string->len));
938 svn_pool_destroy(iterpool);
940 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
942 if (expected_checksum)
944 apr_md5_final(digest, &md5_context);
945 hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
946 if (strcmp(hex_digest, expected_checksum) != 0)
947 return svn_error_createf
948 (SVN_ERR_CHECKSUM_MISMATCH, NULL,
949 _("Checksum mismatch for '%s':\n"
950 " expected checksum: %s\n"
951 " actual checksum: %s\n"),
952 path, expected_checksum, hex_digest);
955 return SVN_NO_ERROR;
958 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
959 apr_hash_t **dirents,
960 svn_revnum_t *fetched_rev,
961 apr_hash_t **props,
962 const char *path,
963 svn_revnum_t rev,
964 apr_uint32_t dirent_fields,
965 apr_pool_t *pool)
967 svn_ra_svn__session_baton_t *sess_baton = session->priv;
968 svn_ra_svn_conn_t *conn = sess_baton->conn;
969 svn_revnum_t crev;
970 apr_array_header_t *proplist, *dirlist;
971 int i;
972 svn_ra_svn_item_t *elt;
973 const char *name, *kind, *cdate, *cauthor;
974 svn_boolean_t has_props;
975 apr_uint64_t size;
976 svn_dirent_t *dirent;
978 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
979 rev, (props != NULL), (dirents != NULL)));
980 if (dirent_fields & SVN_DIRENT_KIND)
982 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
984 if (dirent_fields & SVN_DIRENT_SIZE)
986 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
988 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
990 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
992 if (dirent_fields & SVN_DIRENT_CREATED_REV)
994 SVN_ERR(svn_ra_svn_write_word(conn, pool,
995 SVN_RA_SVN_DIRENT_CREATED_REV));
997 if (dirent_fields & SVN_DIRENT_TIME)
999 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1001 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1003 SVN_ERR(svn_ra_svn_write_word(conn, pool,
1004 SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1006 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1008 SVN_ERR(handle_auth_request(sess_baton, pool));
1009 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "rll", &rev, &proplist,
1010 &dirlist));
1012 if (fetched_rev)
1013 *fetched_rev = rev;
1014 if (props)
1015 SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
1017 /* We're done if dirents aren't wanted. */
1018 if (!dirents)
1019 return SVN_NO_ERROR;
1021 /* Interpret the directory list. */
1022 *dirents = apr_hash_make(pool);
1023 for (i = 0; i < dirlist->nelts; i++)
1025 elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1026 if (elt->kind != SVN_RA_SVN_LIST)
1027 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1028 _("Dirlist element not a list"));
1029 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1030 &name, &kind, &size, &has_props,
1031 &crev, &cdate, &cauthor));
1032 name = svn_path_canonicalize(name, pool);
1033 dirent = apr_palloc(pool, sizeof(*dirent));
1034 SVN_ERR(interpret_kind(kind, pool, &dirent->kind));
1035 dirent->size = size;/* FIXME: svn_filesize_t */
1036 dirent->has_props = has_props;
1037 dirent->created_rev = crev;
1038 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1039 dirent->last_author = cauthor;
1040 apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, dirent);
1043 return SVN_NO_ERROR;
1046 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1047 server, which defaults to youngest. */
1048 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1049 apr_hash_t **mergeinfo,
1050 const apr_array_header_t *paths,
1051 svn_revnum_t revision,
1052 svn_mergeinfo_inheritance_t inherit,
1053 apr_pool_t *pool)
1055 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1056 svn_ra_svn_conn_t *conn = sess_baton->conn;
1057 int i;
1058 apr_array_header_t *mergeinfo_tuple;
1059 svn_ra_svn_item_t *elt;
1060 const char *path, *to_parse;
1061 apr_hash_t *for_path;
1063 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1064 for (i = 0; i < paths->nelts; i++)
1066 path = APR_ARRAY_IDX(paths, i, const char *);
1067 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
1069 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)w)", revision,
1070 svn_inheritance_to_word(inherit)));
1072 SVN_ERR(handle_auth_request(sess_baton, pool));
1073 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &mergeinfo_tuple));
1075 *mergeinfo = NULL;
1076 if (mergeinfo_tuple != NULL && mergeinfo_tuple->nelts > 0)
1078 *mergeinfo = apr_hash_make(pool);
1079 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1081 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1082 if (elt->kind != SVN_RA_SVN_LIST)
1083 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1084 _("Mergeinfo element is not a list"));
1085 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cc",
1086 &path, &to_parse));
1087 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1088 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, for_path);
1092 return SVN_NO_ERROR;
1095 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1096 const svn_ra_reporter3_t **reporter,
1097 void **report_baton, svn_revnum_t rev,
1098 const char *target, svn_depth_t depth,
1099 svn_boolean_t send_copyfrom_args,
1100 const svn_delta_editor_t *update_editor,
1101 void *update_baton, apr_pool_t *pool)
1103 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1104 svn_ra_svn_conn_t *conn = sess_baton->conn;
1105 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1107 /* Tell the server we want to start an update. */
1108 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cbwb", rev, target,
1109 recurse, svn_depth_to_word(depth),
1110 send_copyfrom_args));
1111 SVN_ERR(handle_auth_request(sess_baton, pool));
1113 /* Fetch a reporter for the caller to drive. The reporter will drive
1114 * update_editor upon finish_report(). */
1115 ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1116 target, depth, reporter, report_baton);
1117 return SVN_NO_ERROR;
1120 static svn_error_t *ra_svn_switch(svn_ra_session_t *session,
1121 const svn_ra_reporter3_t **reporter,
1122 void **report_baton, svn_revnum_t rev,
1123 const char *target, svn_depth_t depth,
1124 const char *switch_url,
1125 const svn_delta_editor_t *update_editor,
1126 void *update_baton, apr_pool_t *pool)
1128 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1129 svn_ra_svn_conn_t *conn = sess_baton->conn;
1130 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1132 /* Tell the server we want to start a switch. */
1133 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbcw", rev,
1134 target, recurse, switch_url,
1135 svn_depth_to_word(depth)));
1136 SVN_ERR(handle_auth_request(sess_baton, pool));
1138 /* Fetch a reporter for the caller to drive. The reporter will drive
1139 * update_editor upon finish_report(). */
1140 ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1141 target, depth, reporter, report_baton);
1142 return SVN_NO_ERROR;
1145 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1146 const svn_ra_reporter3_t **reporter,
1147 void **report_baton,
1148 const char *target, svn_revnum_t rev,
1149 svn_depth_t depth,
1150 const svn_delta_editor_t *status_editor,
1151 void *status_baton, apr_pool_t *pool)
1153 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1154 svn_ra_svn_conn_t *conn = sess_baton->conn;
1155 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1157 /* Tell the server we want to start a status operation. */
1158 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)w",
1159 target, recurse, rev,
1160 svn_depth_to_word(depth)));
1161 SVN_ERR(handle_auth_request(sess_baton, pool));
1163 /* Fetch a reporter for the caller to drive. The reporter will drive
1164 * status_editor upon finish_report(). */
1165 ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1166 target, depth, reporter, report_baton);
1167 return SVN_NO_ERROR;
1170 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1171 const svn_ra_reporter3_t **reporter,
1172 void **report_baton,
1173 svn_revnum_t rev, const char *target,
1174 svn_depth_t depth,
1175 svn_boolean_t ignore_ancestry,
1176 svn_boolean_t text_deltas,
1177 const char *versus_url,
1178 const svn_delta_editor_t *diff_editor,
1179 void *diff_baton, apr_pool_t *pool)
1181 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1182 svn_ra_svn_conn_t *conn = sess_baton->conn;
1183 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1185 /* Tell the server we want to start a diff. */
1186 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbcbw", rev,
1187 target, recurse, ignore_ancestry,
1188 versus_url, text_deltas,
1189 svn_depth_to_word(depth)));
1190 SVN_ERR(handle_auth_request(sess_baton, pool));
1192 /* Fetch a reporter for the caller to drive. The reporter will drive
1193 * diff_editor upon finish_report(). */
1194 ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1195 target, depth, reporter, report_baton);
1196 return SVN_NO_ERROR;
1199 static svn_error_t *ra_svn_log(svn_ra_session_t *session,
1200 const apr_array_header_t *paths,
1201 svn_revnum_t start, svn_revnum_t end,
1202 int limit,
1203 svn_boolean_t discover_changed_paths,
1204 svn_boolean_t strict_node_history,
1205 svn_boolean_t include_merged_revisions,
1206 apr_array_header_t *revprops,
1207 svn_log_entry_receiver_t receiver,
1208 void *receiver_baton, apr_pool_t *pool)
1210 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1211 svn_ra_svn_conn_t *conn = sess_baton->conn;
1212 apr_pool_t *subpool;
1213 int i;
1214 const char *path, *cpath, *action, *copy_path;
1215 svn_string_t *author, *date, *message;
1216 svn_ra_svn_item_t *item, *elt;
1217 char *name;
1218 apr_array_header_t *cplist, *rplist;
1219 apr_hash_t *cphash;
1220 svn_revnum_t rev, copy_rev;
1221 svn_log_changed_path_t *change;
1222 int nreceived = 0;
1223 apr_uint64_t has_children_param, invalid_revnum_param;
1224 svn_boolean_t has_children;
1225 svn_log_entry_t *log_entry;
1226 svn_boolean_t want_custom_revprops;
1227 apr_uint64_t revprop_count;
1229 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log"));
1230 if (paths)
1232 for (i = 0; i < paths->nelts; i++)
1234 path = APR_ARRAY_IDX(paths, i, const char *);
1235 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
1238 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1239 discover_changed_paths, strict_node_history,
1240 (apr_uint64_t) limit,
1241 include_merged_revisions));
1242 if (revprops)
1244 want_custom_revprops = FALSE;
1245 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
1246 for (i = 0; i < revprops->nelts; i++)
1248 name = APR_ARRAY_IDX(revprops, i, char *);
1249 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
1250 if (!want_custom_revprops
1251 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
1252 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
1253 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
1254 want_custom_revprops = TRUE;
1256 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1258 else
1260 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
1261 want_custom_revprops = TRUE;
1264 SVN_ERR(handle_auth_request(sess_baton, pool));
1266 /* Read the log messages. */
1267 subpool = svn_pool_create(pool);
1268 while (1)
1270 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
1271 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1272 break;
1273 if (item->kind != SVN_RA_SVN_LIST)
1274 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1275 _("Log entry not a list"));
1276 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool,
1277 "lr(?s)(?s)(?s)?BBnl",
1278 &cplist, &rev, &author, &date,
1279 &message, &has_children_param,
1280 &invalid_revnum_param,
1281 &revprop_count, &rplist));
1282 if (want_custom_revprops && rplist == NULL)
1284 /* Caller asked for custom revprops, but server is too old. */
1285 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1286 _("Server does not support custom revprops"
1287 " via log"));
1290 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1291 has_children = FALSE;
1292 else
1293 has_children = (svn_boolean_t) has_children_param;
1295 /* Because the svn protocol won't let us send an invalid revnum, we have
1296 to recover that fact using the extra parameter. */
1297 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1298 && invalid_revnum_param == TRUE)
1299 rev = SVN_INVALID_REVNUM;
1301 if (cplist->nelts > 0)
1303 /* Interpret the changed-paths list. */
1304 cphash = apr_hash_make(subpool);
1305 for (i = 0; i < cplist->nelts; i++)
1307 elt = &APR_ARRAY_IDX(cplist, i, svn_ra_svn_item_t);
1308 if (elt->kind != SVN_RA_SVN_LIST)
1309 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1310 _("Changed-path entry not a list"));
1311 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "cw(?cr)",
1312 &cpath, &action, &copy_path,
1313 &copy_rev));
1314 cpath = svn_path_canonicalize(cpath, subpool);
1315 if (copy_path)
1316 copy_path = svn_path_canonicalize(copy_path, subpool);
1317 change = apr_palloc(subpool, sizeof(*change));
1318 change->action = *action;
1319 change->copyfrom_path = copy_path;
1320 change->copyfrom_rev = copy_rev;
1321 apr_hash_set(cphash, cpath, APR_HASH_KEY_STRING, change);
1324 else
1325 cphash = NULL;
1327 if (! (limit && ++nreceived > limit))
1329 log_entry = svn_log_entry_create(subpool);
1331 log_entry->changed_paths = cphash;
1332 log_entry->revision = rev;
1333 log_entry->has_children = has_children;
1334 if (rplist)
1335 SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
1336 &log_entry->revprops));
1337 if (log_entry->revprops == NULL)
1338 log_entry->revprops = apr_hash_make(pool);
1339 if (revprops == NULL)
1341 /* Caller requested all revprops; set author/date/log. */
1342 if (author)
1343 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1344 APR_HASH_KEY_STRING, author);
1345 if (date)
1346 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
1347 APR_HASH_KEY_STRING, date);
1348 if (message)
1349 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
1350 APR_HASH_KEY_STRING, message);
1352 else
1354 /* Caller requested some; maybe set author/date/log. */
1355 for (i = 0; i < revprops->nelts; i++)
1357 name = APR_ARRAY_IDX(revprops, i, char *);
1358 if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1359 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1360 APR_HASH_KEY_STRING, author);
1361 if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1362 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
1363 APR_HASH_KEY_STRING, date);
1364 if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1365 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
1366 APR_HASH_KEY_STRING, message);
1369 SVN_ERR(receiver(receiver_baton, log_entry, subpool));
1371 svn_pool_clear(subpool);
1373 svn_pool_destroy(subpool);
1375 /* Read the response. */
1376 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1378 return SVN_NO_ERROR;
1382 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1383 const char *path, svn_revnum_t rev,
1384 svn_node_kind_t *kind, apr_pool_t *pool)
1386 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1387 svn_ra_svn_conn_t *conn = sess_baton->conn;
1388 const char *kind_word;
1390 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev));
1391 SVN_ERR(handle_auth_request(sess_baton, pool));
1392 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word));
1393 SVN_ERR(interpret_kind(kind_word, pool, kind));
1394 return SVN_NO_ERROR;
1398 /* If ERR is a command not supported error, wrap it in a
1399 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1400 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1401 const char *msg)
1403 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1404 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1405 msg);
1406 return err;
1410 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1411 const char *path, svn_revnum_t rev,
1412 svn_dirent_t **dirent, apr_pool_t *pool)
1414 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1415 svn_ra_svn_conn_t *conn = sess_baton->conn;
1416 apr_array_header_t *list = NULL;
1417 const char *kind, *cdate, *cauthor;
1418 svn_revnum_t crev;
1419 svn_boolean_t has_props;
1420 apr_uint64_t size;
1421 svn_dirent_t *the_dirent;
1423 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "stat", "c(?r)", path, rev));
1425 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1426 _("'stat' not implemented")));
1428 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list));
1430 if (! list)
1432 *dirent = NULL;
1434 else
1436 SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "wnbr(?c)(?c)",
1437 &kind, &size, &has_props,
1438 &crev, &cdate, &cauthor));
1440 the_dirent = apr_palloc(pool, sizeof(*the_dirent));
1441 SVN_ERR(interpret_kind(kind, pool, &the_dirent->kind));
1442 the_dirent->size = size;/* FIXME: svn_filesize_t */
1443 the_dirent->has_props = has_props;
1444 the_dirent->created_rev = crev;
1445 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1446 the_dirent->last_author = cauthor;
1448 *dirent = the_dirent;
1451 return SVN_NO_ERROR;
1455 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1456 apr_hash_t **locations,
1457 const char *path,
1458 svn_revnum_t peg_revision,
1459 apr_array_header_t *location_revisions,
1460 apr_pool_t *pool)
1462 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1463 svn_ra_svn_conn_t *conn = sess_baton->conn;
1464 svn_revnum_t revision;
1465 svn_ra_svn_item_t *item;
1466 svn_boolean_t is_done;
1467 int i;
1468 const char *ret_path;
1470 /* Transmit the parameters. */
1471 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cr(!",
1472 "get-locations", path, peg_revision));
1473 for (i = 0; i < location_revisions->nelts; i++)
1475 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1476 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!r!", revision));
1479 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1481 /* Servers before 1.1 don't support this command. Check for this here. */
1482 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1483 _("'get-locations' not implemented")));
1485 /* Read the hash items. */
1486 is_done = FALSE;
1487 *locations = apr_hash_make(pool);
1488 while (!is_done)
1490 SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
1491 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1492 is_done = 1;
1493 else if (item->kind != SVN_RA_SVN_LIST)
1494 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1495 _("Location entry not a list"));
1496 else
1498 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "rc",
1499 &revision, &ret_path));
1500 ret_path = svn_path_canonicalize(ret_path, pool);
1501 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1502 sizeof(revision)),
1503 sizeof(revision), ret_path);
1507 /* Read the response. This is so the server would have a chance to
1508 * report an error. */
1509 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1511 return SVN_NO_ERROR;
1514 static svn_error_t *
1515 ra_svn_get_location_segments(svn_ra_session_t *session,
1516 const char *path,
1517 svn_revnum_t peg_revision,
1518 svn_revnum_t start_rev,
1519 svn_revnum_t end_rev,
1520 svn_location_segment_receiver_t receiver,
1521 void *receiver_baton,
1522 apr_pool_t *pool)
1524 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1525 svn_ra_svn_conn_t *conn = sess_baton->conn;
1526 svn_ra_svn_item_t *item;
1527 svn_boolean_t is_done;
1528 svn_revnum_t range_start, range_end;
1529 const char *ret_path;
1530 svn_location_segment_t *segment;
1531 apr_pool_t *subpool = svn_pool_create(pool);
1533 /* Transmit the parameters. */
1534 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
1535 "get-location-segments",
1536 path, peg_revision, start_rev, end_rev));
1538 /* Servers before 1.1 don't support this command. Check for this here. */
1539 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1540 _("'get-location-segments' not implemented")));
1542 /* Parse the response. */
1543 is_done = FALSE;
1544 while (!is_done)
1546 svn_pool_clear(subpool);
1547 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
1548 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1549 is_done = 1;
1550 else if (item->kind != SVN_RA_SVN_LIST)
1551 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1552 _("Location segment entry not a list"));
1553 else
1555 segment = apr_pcalloc(subpool, sizeof(*segment));
1556 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "rr(?c)",
1557 &range_start, &range_end, &ret_path));
1558 if (! (SVN_IS_VALID_REVNUM(range_start)
1559 && SVN_IS_VALID_REVNUM(range_end)))
1560 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1561 _("Expected valid revision range"));
1562 if (ret_path)
1563 ret_path = svn_path_canonicalize(ret_path, subpool);
1564 segment->path = ret_path;
1565 segment->range_start = range_start;
1566 segment->range_end = range_end;
1567 SVN_ERR(receiver(segment, receiver_baton, subpool));
1570 svn_pool_destroy(subpool);
1572 /* Read the response. This is so the server would have a chance to
1573 * report an error. */
1574 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1576 return SVN_NO_ERROR;
1579 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
1580 const char *path,
1581 svn_revnum_t start, svn_revnum_t end,
1582 svn_boolean_t include_merged_revisions,
1583 svn_file_rev_handler_t handler,
1584 void *handler_baton, apr_pool_t *pool)
1586 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1587 apr_pool_t *rev_pool, *chunk_pool;
1588 svn_ra_svn_item_t *item;
1589 const char *p;
1590 svn_revnum_t rev;
1591 apr_array_header_t *rev_proplist, *proplist;
1592 apr_hash_t *rev_props;
1593 apr_array_header_t *props;
1594 svn_boolean_t has_txdelta;
1595 svn_boolean_t had_revision = FALSE;
1596 svn_stream_t *stream;
1597 svn_txdelta_window_handler_t d_handler;
1598 void *d_baton;
1599 apr_size_t size;
1600 apr_uint64_t merged_rev_param;
1601 svn_boolean_t merged_rev;
1603 /* One sub-pool for each revision and one for each txdelta chunk.
1604 Note that the rev_pool must live during the following txdelta. */
1605 rev_pool = svn_pool_create(pool);
1606 chunk_pool = svn_pool_create(pool);
1608 SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs",
1609 "c(?r)(?r)b", path, start, end,
1610 include_merged_revisions));
1612 /* Servers before 1.1 don't support this command. Check for this here. */
1613 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1614 _("'get-file-revs' not implemented")));
1616 while (1)
1618 svn_pool_clear(rev_pool);
1619 svn_pool_clear(chunk_pool);
1620 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, rev_pool, &item));
1621 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1622 break;
1623 /* Either we've got a correct revision or we will error out below. */
1624 had_revision = TRUE;
1625 if (item->kind != SVN_RA_SVN_LIST)
1626 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1627 _("Revision entry not a list"));
1629 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, rev_pool,
1630 "crll?B", &p, &rev, &rev_proplist,
1631 &proplist, &merged_rev_param));
1632 p = svn_path_canonicalize(p, rev_pool);
1633 SVN_ERR(svn_ra_svn_parse_proplist(rev_proplist, rev_pool, &rev_props));
1634 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
1635 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1636 merged_rev = FALSE;
1637 else
1638 merged_rev = (svn_boolean_t) merged_rev_param;
1640 /* Get the first delta chunk so we know if there is a delta. */
1641 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item));
1642 if (item->kind != SVN_RA_SVN_STRING)
1643 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1644 _("Text delta chunk not a string"));
1645 has_txdelta = item->u.string->len > 0;
1647 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
1648 has_txdelta ? &d_handler : NULL, &d_baton,
1649 props, rev_pool));
1651 /* Process the text delta if any. */
1652 if (has_txdelta)
1654 if (d_handler)
1655 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
1656 rev_pool);
1657 else
1658 stream = NULL;
1659 while (item->u.string->len > 0)
1661 size = item->u.string->len;
1662 if (stream)
1663 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
1664 svn_pool_clear(chunk_pool);
1666 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item));
1667 if (item->kind != SVN_RA_SVN_STRING)
1668 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1669 _("Text delta chunk not a string"));
1671 if (stream)
1672 SVN_ERR(svn_stream_close(stream));
1676 SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton->conn, pool, ""));
1678 /* Return error if we didn't get any revisions. */
1679 if (!had_revision)
1680 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1681 _("The get-file-revs command didn't return "
1682 "any revisions"));
1684 svn_pool_destroy(chunk_pool);
1685 svn_pool_destroy(rev_pool);
1687 return SVN_NO_ERROR;
1690 /* For each path in PATH_REVS, send a 'lock' command to the server.
1691 Used with 1.2.x series servers which support locking, but of only
1692 one path at a time. ra_svn_lock(), which supports 'lock-many'
1693 is now the default. See svn_ra_lock() docstring for interface details. */
1694 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
1695 apr_hash_t *path_revs,
1696 const char *comment,
1697 svn_boolean_t steal_lock,
1698 svn_ra_lock_callback_t lock_func,
1699 void *lock_baton,
1700 apr_pool_t *pool)
1702 svn_ra_svn__session_baton_t *sess = session->priv;
1703 svn_ra_svn_conn_t* conn = sess->conn;
1704 apr_array_header_t *list;
1705 apr_hash_index_t *hi;
1706 apr_pool_t *iterpool = svn_pool_create(pool);
1708 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1710 svn_lock_t *lock;
1711 const void *key;
1712 const char *path;
1713 void *val;
1714 svn_revnum_t *revnum;
1715 svn_error_t *err, *callback_err = NULL;
1717 svn_pool_clear(iterpool);
1719 apr_hash_this(hi, &key, NULL, &val);
1720 path = key;
1721 revnum = val;
1723 SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "lock", "c(?c)b(?r)",
1724 path, comment,
1725 steal_lock, *revnum));
1727 /* Servers before 1.2 doesn't support locking. Check this here. */
1728 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
1729 _("Server doesn't support "
1730 "the lock command")));
1732 err = svn_ra_svn_read_cmd_response(conn, iterpool, "l", &list);
1734 if (!err)
1735 SVN_ERR(parse_lock(list, iterpool, &lock));
1737 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1738 return err;
1740 if (lock_func)
1741 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1742 err, iterpool);
1744 svn_error_clear(err);
1746 if (callback_err)
1747 return callback_err;
1750 svn_pool_destroy(iterpool);
1752 return SVN_NO_ERROR;
1755 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
1756 Used with 1.2.x series servers which support unlocking, but of only
1757 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
1758 now the default. See svn_ra_unlock() docstring for interface details. */
1759 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
1760 apr_hash_t *path_tokens,
1761 svn_boolean_t break_lock,
1762 svn_ra_lock_callback_t lock_func,
1763 void *lock_baton,
1764 apr_pool_t *pool)
1766 svn_ra_svn__session_baton_t *sess = session->priv;
1767 svn_ra_svn_conn_t* conn = sess->conn;
1768 apr_hash_index_t *hi;
1769 apr_pool_t *iterpool = svn_pool_create(pool);
1771 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1773 const void *key;
1774 const char *path;
1775 void *val;
1776 const char *token;
1777 svn_error_t *err, *callback_err = NULL;
1779 svn_pool_clear(iterpool);
1781 apr_hash_this(hi, &key, NULL, &val);
1782 path = key;
1783 if (strcmp(val, "") != 0)
1784 token = val;
1785 else
1786 token = NULL;
1788 SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "unlock", "c(?c)b",
1789 path, token, break_lock));
1791 /* Servers before 1.2 don't support locking. Check this here. */
1792 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
1793 _("Server doesn't support the unlock "
1794 "command")));
1796 err = svn_ra_svn_read_cmd_response(conn, iterpool, "");
1798 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1799 return err;
1801 if (lock_func)
1802 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
1804 svn_error_clear(err);
1806 if (callback_err)
1807 return callback_err;
1810 svn_pool_destroy(iterpool);
1812 return SVN_NO_ERROR;
1815 /* Tell the server to lock all paths in PATH_REVS.
1816 See svn_ra_lock() for interface details. */
1817 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
1818 apr_hash_t *path_revs,
1819 const char *comment,
1820 svn_boolean_t steal_lock,
1821 svn_ra_lock_callback_t lock_func,
1822 void *lock_baton,
1823 apr_pool_t *pool)
1825 svn_ra_svn__session_baton_t *sess = session->priv;
1826 svn_ra_svn_conn_t *conn = sess->conn;
1827 apr_hash_index_t *hi;
1828 svn_ra_svn_item_t *elt;
1829 svn_error_t *err, *callback_err = SVN_NO_ERROR;
1830 apr_pool_t *subpool = svn_pool_create(pool);
1831 const char *status;
1832 svn_lock_t *lock;
1833 apr_array_header_t *list = NULL;
1835 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)b(!", "lock-many",
1836 comment, steal_lock));
1838 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1840 const void *key;
1841 const char *path;
1842 void *val;
1843 svn_revnum_t *revnum;
1845 svn_pool_clear(subpool);
1846 apr_hash_this(hi, &key, NULL, &val);
1847 path = key;
1848 revnum = val;
1850 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?r)", path, *revnum));
1853 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1855 err = handle_auth_request(sess, pool);
1857 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
1858 * to 'lock'. */
1859 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1861 svn_error_clear(err);
1862 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
1863 lock_func, lock_baton, pool);
1866 if (err)
1867 return err;
1869 /* Loop over responses to get lock information. */
1870 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1872 const void *key;
1873 const char *path;
1875 apr_hash_this(hi, &key, NULL, NULL);
1876 path = key;
1878 svn_pool_clear(subpool);
1879 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &elt));
1881 /* The server might have encountered some sort of fatal error in
1882 the middle of the request list. If this happens, it will
1883 transmit "done" to end the lock-info early, and then the
1884 overall command response will talk about the fatal error. */
1885 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
1886 break;
1888 if (elt->kind != SVN_RA_SVN_LIST)
1889 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1890 _("Lock response not a list"));
1892 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "wl", &status,
1893 &list));
1895 if (strcmp(status, "failure") == 0)
1896 err = svn_ra_svn__handle_failure_status(list, subpool);
1897 else if (strcmp(status, "success") == 0)
1899 SVN_ERR(parse_lock(list, subpool, &lock));
1900 err = NULL;
1902 else
1903 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1904 _("Unknown status for lock command"));
1906 if (lock_func)
1907 callback_err = lock_func(lock_baton, path, TRUE,
1908 err ? NULL : lock,
1909 err, subpool);
1910 else
1911 callback_err = SVN_NO_ERROR;
1913 svn_error_clear(err);
1915 if (callback_err)
1916 return callback_err;
1919 /* If we didn't break early above, and the whole hash was traversed,
1920 read the final "done" from the server. */
1921 if (!hi)
1923 SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt));
1924 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
1925 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1926 _("Didn't receive end marker for lock "
1927 "responses"));
1930 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1932 svn_pool_destroy(subpool);
1934 return SVN_NO_ERROR;
1937 /* Tell the server to unlock all paths in PATH_TOKENS.
1938 See svn_ra_unlock() for interface details. */
1939 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
1940 apr_hash_t *path_tokens,
1941 svn_boolean_t break_lock,
1942 svn_ra_lock_callback_t lock_func,
1943 void *lock_baton,
1944 apr_pool_t *pool)
1946 svn_ra_svn__session_baton_t *sess = session->priv;
1947 svn_ra_svn_conn_t *conn = sess->conn;
1948 apr_hash_index_t *hi;
1949 apr_pool_t *subpool = svn_pool_create(pool);
1950 svn_error_t *err, *callback_err = NULL;
1951 svn_ra_svn_item_t *elt;
1952 const char *status = NULL;
1953 apr_array_header_t *list = NULL;
1954 const void *key;
1955 const char *path;
1957 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(b(!", "unlock-many",
1958 break_lock));
1960 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1962 void *val;
1963 const char *token;
1965 svn_pool_clear(subpool);
1966 apr_hash_this(hi, &key, NULL, &val);
1967 path = key;
1969 if (strcmp(val, "") != 0)
1970 token = val;
1971 else
1972 token = NULL;
1974 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?c)", path, token));
1977 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1979 err = handle_auth_request(sess, pool);
1981 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
1982 * to 'unlock'.
1984 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1986 svn_error_clear(err);
1987 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
1988 lock_baton, pool);
1991 if (err)
1992 return err;
1994 /* Loop over responses to unlock files. */
1995 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1997 svn_pool_clear(subpool);
1999 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &elt));
2001 /* The server might have encountered some sort of fatal error in
2002 the middle of the request list. If this happens, it will
2003 transmit "done" to end the lock-info early, and then the
2004 overall command response will talk about the fatal error. */
2005 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2006 break;
2008 apr_hash_this(hi, &key, NULL, NULL);
2009 path = key;
2011 if (elt->kind != SVN_RA_SVN_LIST)
2012 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2013 _("Unlock response not a list"));
2015 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "wl", &status,
2016 &list));
2018 if (strcmp(status, "failure") == 0)
2019 err = svn_ra_svn__handle_failure_status(list, subpool);
2020 else if (strcmp(status, "success") == 0)
2022 SVN_ERR(svn_ra_svn_parse_tuple(list, subpool, "c", &path));
2023 err = SVN_NO_ERROR;
2025 else
2026 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2027 _("Unknown status for unlock command"));
2029 if (lock_func)
2030 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2031 subpool);
2032 else
2033 callback_err = SVN_NO_ERROR;
2035 svn_error_clear(err);
2037 if (callback_err)
2038 return callback_err;
2041 /* If we didn't break early above, and the whole hash was traversed,
2042 read the final "done" from the server. */
2043 if (!hi)
2045 SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt));
2046 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2047 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2048 _("Didn't receive end marker for unlock "
2049 "responses"));
2052 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
2054 svn_pool_destroy(subpool);
2056 return SVN_NO_ERROR;
2059 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2060 svn_lock_t **lock,
2061 const char *path,
2062 apr_pool_t *pool)
2064 svn_ra_svn__session_baton_t *sess = session->priv;
2065 svn_ra_svn_conn_t* conn = sess->conn;
2066 apr_array_header_t *list;
2068 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-lock", "c", path));
2070 /* Servers before 1.2 doesn't support locking. Check this here. */
2071 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2072 _("Server doesn't support the get-lock "
2073 "command")));
2075 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list));
2076 if (list)
2077 SVN_ERR(parse_lock(list, pool, lock));
2078 else
2079 *lock = NULL;
2081 return SVN_NO_ERROR;
2084 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2085 apr_hash_t **locks,
2086 const char *path,
2087 apr_pool_t *pool)
2089 svn_ra_svn__session_baton_t *sess = session->priv;
2090 svn_ra_svn_conn_t* conn = sess->conn;
2091 apr_array_header_t *list;
2092 int i;
2093 svn_ra_svn_item_t *elt;
2094 svn_lock_t *lock;
2096 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-locks", "c", path));
2098 /* Servers before 1.2 doesn't support locking. Check this here. */
2099 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2100 _("Server doesn't support the get-lock "
2101 "command")));
2103 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &list));
2105 *locks = apr_hash_make(pool);
2107 for (i = 0; i < list->nelts; ++i)
2109 elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2111 if (elt->kind != SVN_RA_SVN_LIST)
2112 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2113 _("Lock element not a list"));
2114 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2115 apr_hash_set(*locks, lock->path, APR_HASH_KEY_STRING, lock);
2118 return SVN_NO_ERROR;
2122 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2123 svn_revnum_t revision,
2124 svn_revnum_t low_water_mark,
2125 svn_boolean_t send_deltas,
2126 const svn_delta_editor_t *editor,
2127 void *edit_baton,
2128 apr_pool_t *pool)
2130 svn_ra_svn__session_baton_t *sess = session->priv;
2132 SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay", "rrb", revision,
2133 low_water_mark, send_deltas));
2135 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2136 _("Server doesn't support the replay "
2137 "command")));
2139 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2140 NULL, TRUE));
2142 SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
2144 return SVN_NO_ERROR;
2148 static svn_error_t *
2149 ra_svn_replay_range(svn_ra_session_t *session,
2150 svn_revnum_t start_revision,
2151 svn_revnum_t end_revision,
2152 svn_revnum_t low_water_mark,
2153 svn_boolean_t send_deltas,
2154 svn_ra_replay_revstart_callback_t revstart_func,
2155 svn_ra_replay_revfinish_callback_t revfinish_func,
2156 void *replay_baton,
2157 apr_pool_t *pool)
2159 svn_ra_svn__session_baton_t *sess = session->priv;
2160 apr_pool_t *iterpool;
2161 svn_revnum_t rev;
2163 SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay-range", "rrrb",
2164 start_revision, end_revision,
2165 low_water_mark, send_deltas));
2167 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2168 _("Server doesn't support the replay-range "
2169 "command")));
2171 iterpool = svn_pool_create(pool);
2172 for (rev = start_revision; rev <= end_revision; rev++)
2174 const svn_delta_editor_t *editor;
2175 void *edit_baton;
2176 apr_hash_t *rev_props;
2177 svn_ra_svn_item_t *item;
2179 svn_pool_clear(iterpool);
2181 SVN_ERR(svn_ra_svn_read_item(sess->conn, iterpool, &item));
2182 if (item->kind != SVN_RA_SVN_LIST)
2183 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2184 _("Revision properties not a list"));
2186 SVN_ERR(svn_ra_svn_parse_proplist(item->u.list, iterpool, &rev_props));
2188 SVN_ERR(revstart_func(rev, replay_baton,
2189 &editor, &edit_baton,
2190 rev_props,
2191 iterpool));
2192 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2193 editor, edit_baton,
2194 NULL, TRUE));
2195 SVN_ERR(revfinish_func(rev, replay_baton,
2196 editor, edit_baton,
2197 rev_props,
2198 iterpool));
2200 svn_pool_destroy(iterpool);
2202 SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
2204 return SVN_NO_ERROR;
2208 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
2209 svn_boolean_t *has,
2210 const char *capability,
2211 apr_pool_t *pool)
2213 svn_ra_svn__session_baton_t *sess = session->priv;
2215 *has = FALSE;
2217 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0)
2218 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_DEPTH);
2219 else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
2220 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_MERGEINFO);
2221 else if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0)
2222 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_LOG_REVPROPS);
2223 else /* Don't know any other capabilities, so error. */
2225 return svn_error_createf
2226 (SVN_ERR_RA_UNKNOWN_CAPABILITY, NULL,
2227 _("Don't know anything about capability '%s'"), capability);
2230 return SVN_NO_ERROR;
2234 static const svn_ra__vtable_t ra_svn_vtable = {
2235 svn_ra_svn_version,
2236 ra_svn_get_description,
2237 ra_svn_get_schemes,
2238 ra_svn_open,
2239 ra_svn_reparent,
2240 ra_svn_get_session_url,
2241 ra_svn_get_latest_rev,
2242 ra_svn_get_dated_rev,
2243 ra_svn_change_rev_prop,
2244 ra_svn_rev_proplist,
2245 ra_svn_rev_prop,
2246 ra_svn_commit,
2247 ra_svn_get_file,
2248 ra_svn_get_dir,
2249 ra_svn_get_mergeinfo,
2250 ra_svn_update,
2251 ra_svn_switch,
2252 ra_svn_status,
2253 ra_svn_diff,
2254 ra_svn_log,
2255 ra_svn_check_path,
2256 ra_svn_stat,
2257 ra_svn_get_uuid,
2258 ra_svn_get_repos_root,
2259 ra_svn_get_locations,
2260 ra_svn_get_location_segments,
2261 ra_svn_get_file_revs,
2262 ra_svn_lock,
2263 ra_svn_unlock,
2264 ra_svn_get_lock,
2265 ra_svn_get_locks,
2266 ra_svn_replay,
2267 ra_svn_has_capability,
2268 ra_svn_replay_range,
2271 svn_error_t *
2272 svn_ra_svn__init(const svn_version_t *loader_version,
2273 const svn_ra__vtable_t **vtable,
2274 apr_pool_t *pool)
2276 static const svn_version_checklist_t checklist[] =
2278 { "svn_subr", svn_subr_version },
2279 { "svn_delta", svn_delta_version },
2280 { NULL, NULL }
2283 SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist));
2285 /* Simplified version check to make sure we can safely use the
2286 VTABLE parameter. The RA loader does a more exhaustive check. */
2287 if (loader_version->major != SVN_VER_MAJOR)
2289 return svn_error_createf
2290 (SVN_ERR_VERSION_MISMATCH, NULL,
2291 _("Unsupported RA loader version (%d) for ra_svn"),
2292 loader_version->major);
2295 *vtable = &ra_svn_vtable;
2297 #ifdef SVN_HAVE_SASL
2298 SVN_ERR(svn_ra_svn__sasl_init());
2299 #endif
2301 return SVN_NO_ERROR;
2304 /* Compatibility wrapper for the 1.1 and before API. */
2305 #define NAME "ra_svn"
2306 #define DESCRIPTION RA_SVN_DESCRIPTION
2307 #define VTBL ra_svn_vtable
2308 #define INITFUNC svn_ra_svn__init
2309 #define COMPAT_INITFUNC svn_ra_svn_init
2310 #include "../libsvn_ra/wrapper_template.h"