Remove no-longer-used svn_*_get_mergeinfo_for_tree APIs.
[svn.git] / subversion / libsvn_ra_svn / client.c
blob32bf7614e8ed822753e4bf2afc2f38bdea6a1465
1 /*
2 * client.c : Functions for repository access via the Subversion protocol
4 * ====================================================================
5 * Copyright (c) 2000-2008 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 svn_boolean_t include_descendants,
1054 apr_pool_t *pool)
1056 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1057 svn_ra_svn_conn_t *conn = sess_baton->conn;
1058 int i;
1059 apr_array_header_t *mergeinfo_tuple;
1060 svn_ra_svn_item_t *elt;
1061 const char *path, *to_parse;
1062 apr_hash_t *for_path;
1064 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1065 for (i = 0; i < paths->nelts; i++)
1067 path = APR_ARRAY_IDX(paths, i, const char *);
1068 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
1070 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)wb)", revision,
1071 svn_inheritance_to_word(inherit),
1072 include_descendants));
1074 SVN_ERR(handle_auth_request(sess_baton, pool));
1075 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &mergeinfo_tuple));
1077 *mergeinfo = NULL;
1078 if (mergeinfo_tuple != NULL && mergeinfo_tuple->nelts > 0)
1080 *mergeinfo = apr_hash_make(pool);
1081 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1083 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1084 if (elt->kind != SVN_RA_SVN_LIST)
1085 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1086 _("Mergeinfo element is not a list"));
1087 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cc",
1088 &path, &to_parse));
1089 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1090 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, for_path);
1094 return SVN_NO_ERROR;
1097 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1098 const svn_ra_reporter3_t **reporter,
1099 void **report_baton, svn_revnum_t rev,
1100 const char *target, svn_depth_t depth,
1101 svn_boolean_t send_copyfrom_args,
1102 const svn_delta_editor_t *update_editor,
1103 void *update_baton, apr_pool_t *pool)
1105 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1106 svn_ra_svn_conn_t *conn = sess_baton->conn;
1107 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1109 /* Tell the server we want to start an update. */
1110 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cbwb", rev, target,
1111 recurse, svn_depth_to_word(depth),
1112 send_copyfrom_args));
1113 SVN_ERR(handle_auth_request(sess_baton, pool));
1115 /* Fetch a reporter for the caller to drive. The reporter will drive
1116 * update_editor upon finish_report(). */
1117 ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1118 target, depth, reporter, report_baton);
1119 return SVN_NO_ERROR;
1122 static svn_error_t *ra_svn_switch(svn_ra_session_t *session,
1123 const svn_ra_reporter3_t **reporter,
1124 void **report_baton, svn_revnum_t rev,
1125 const char *target, svn_depth_t depth,
1126 const char *switch_url,
1127 const svn_delta_editor_t *update_editor,
1128 void *update_baton, apr_pool_t *pool)
1130 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1131 svn_ra_svn_conn_t *conn = sess_baton->conn;
1132 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1134 /* Tell the server we want to start a switch. */
1135 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbcw", rev,
1136 target, recurse, switch_url,
1137 svn_depth_to_word(depth)));
1138 SVN_ERR(handle_auth_request(sess_baton, pool));
1140 /* Fetch a reporter for the caller to drive. The reporter will drive
1141 * update_editor upon finish_report(). */
1142 ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1143 target, depth, reporter, report_baton);
1144 return SVN_NO_ERROR;
1147 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1148 const svn_ra_reporter3_t **reporter,
1149 void **report_baton,
1150 const char *target, svn_revnum_t rev,
1151 svn_depth_t depth,
1152 const svn_delta_editor_t *status_editor,
1153 void *status_baton, apr_pool_t *pool)
1155 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1156 svn_ra_svn_conn_t *conn = sess_baton->conn;
1157 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1159 /* Tell the server we want to start a status operation. */
1160 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)w",
1161 target, recurse, rev,
1162 svn_depth_to_word(depth)));
1163 SVN_ERR(handle_auth_request(sess_baton, pool));
1165 /* Fetch a reporter for the caller to drive. The reporter will drive
1166 * status_editor upon finish_report(). */
1167 ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1168 target, depth, reporter, report_baton);
1169 return SVN_NO_ERROR;
1172 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1173 const svn_ra_reporter3_t **reporter,
1174 void **report_baton,
1175 svn_revnum_t rev, const char *target,
1176 svn_depth_t depth,
1177 svn_boolean_t ignore_ancestry,
1178 svn_boolean_t text_deltas,
1179 const char *versus_url,
1180 const svn_delta_editor_t *diff_editor,
1181 void *diff_baton, apr_pool_t *pool)
1183 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1184 svn_ra_svn_conn_t *conn = sess_baton->conn;
1185 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1187 /* Tell the server we want to start a diff. */
1188 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbcbw", rev,
1189 target, recurse, ignore_ancestry,
1190 versus_url, text_deltas,
1191 svn_depth_to_word(depth)));
1192 SVN_ERR(handle_auth_request(sess_baton, pool));
1194 /* Fetch a reporter for the caller to drive. The reporter will drive
1195 * diff_editor upon finish_report(). */
1196 ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1197 target, depth, reporter, report_baton);
1198 return SVN_NO_ERROR;
1201 static svn_error_t *ra_svn_log(svn_ra_session_t *session,
1202 const apr_array_header_t *paths,
1203 svn_revnum_t start, svn_revnum_t end,
1204 int limit,
1205 svn_boolean_t discover_changed_paths,
1206 svn_boolean_t strict_node_history,
1207 svn_boolean_t include_merged_revisions,
1208 const apr_array_header_t *revprops,
1209 svn_log_entry_receiver_t receiver,
1210 void *receiver_baton, apr_pool_t *pool)
1212 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1213 svn_ra_svn_conn_t *conn = sess_baton->conn;
1214 apr_pool_t *subpool;
1215 int i;
1216 const char *path, *cpath, *action, *copy_path;
1217 svn_string_t *author, *date, *message;
1218 svn_ra_svn_item_t *item, *elt;
1219 char *name;
1220 apr_array_header_t *cplist, *rplist;
1221 apr_hash_t *cphash;
1222 svn_revnum_t rev, copy_rev;
1223 svn_log_changed_path_t *change;
1224 int nreceived = 0;
1225 apr_uint64_t has_children_param, invalid_revnum_param;
1226 svn_boolean_t has_children;
1227 svn_log_entry_t *log_entry;
1228 svn_boolean_t want_custom_revprops;
1229 apr_uint64_t revprop_count;
1231 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log"));
1232 if (paths)
1234 for (i = 0; i < paths->nelts; i++)
1236 path = APR_ARRAY_IDX(paths, i, const char *);
1237 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
1240 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1241 discover_changed_paths, strict_node_history,
1242 (apr_uint64_t) limit,
1243 include_merged_revisions));
1244 if (revprops)
1246 want_custom_revprops = FALSE;
1247 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
1248 for (i = 0; i < revprops->nelts; i++)
1250 name = APR_ARRAY_IDX(revprops, i, char *);
1251 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
1252 if (!want_custom_revprops
1253 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
1254 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
1255 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
1256 want_custom_revprops = TRUE;
1258 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1260 else
1262 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
1263 want_custom_revprops = TRUE;
1266 SVN_ERR(handle_auth_request(sess_baton, pool));
1268 /* Read the log messages. */
1269 subpool = svn_pool_create(pool);
1270 while (1)
1272 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
1273 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1274 break;
1275 if (item->kind != SVN_RA_SVN_LIST)
1276 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1277 _("Log entry not a list"));
1278 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool,
1279 "lr(?s)(?s)(?s)?BBnl",
1280 &cplist, &rev, &author, &date,
1281 &message, &has_children_param,
1282 &invalid_revnum_param,
1283 &revprop_count, &rplist));
1284 if (want_custom_revprops && rplist == NULL)
1286 /* Caller asked for custom revprops, but server is too old. */
1287 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1288 _("Server does not support custom revprops"
1289 " via log"));
1292 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1293 has_children = FALSE;
1294 else
1295 has_children = (svn_boolean_t) has_children_param;
1297 /* Because the svn protocol won't let us send an invalid revnum, we have
1298 to recover that fact using the extra parameter. */
1299 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1300 && invalid_revnum_param == TRUE)
1301 rev = SVN_INVALID_REVNUM;
1303 if (cplist->nelts > 0)
1305 /* Interpret the changed-paths list. */
1306 cphash = apr_hash_make(subpool);
1307 for (i = 0; i < cplist->nelts; i++)
1309 elt = &APR_ARRAY_IDX(cplist, i, svn_ra_svn_item_t);
1310 if (elt->kind != SVN_RA_SVN_LIST)
1311 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1312 _("Changed-path entry not a list"));
1313 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "cw(?cr)",
1314 &cpath, &action, &copy_path,
1315 &copy_rev));
1316 cpath = svn_path_canonicalize(cpath, subpool);
1317 if (copy_path)
1318 copy_path = svn_path_canonicalize(copy_path, subpool);
1319 change = apr_palloc(subpool, sizeof(*change));
1320 change->action = *action;
1321 change->copyfrom_path = copy_path;
1322 change->copyfrom_rev = copy_rev;
1323 apr_hash_set(cphash, cpath, APR_HASH_KEY_STRING, change);
1326 else
1327 cphash = NULL;
1329 if (! (limit && ++nreceived > limit))
1331 log_entry = svn_log_entry_create(subpool);
1333 log_entry->changed_paths = cphash;
1334 log_entry->revision = rev;
1335 log_entry->has_children = has_children;
1336 if (rplist)
1337 SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
1338 &log_entry->revprops));
1339 if (log_entry->revprops == NULL)
1340 log_entry->revprops = apr_hash_make(pool);
1341 if (revprops == NULL)
1343 /* Caller requested all revprops; set author/date/log. */
1344 if (author)
1345 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1346 APR_HASH_KEY_STRING, author);
1347 if (date)
1348 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
1349 APR_HASH_KEY_STRING, date);
1350 if (message)
1351 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
1352 APR_HASH_KEY_STRING, message);
1354 else
1356 /* Caller requested some; maybe set author/date/log. */
1357 for (i = 0; i < revprops->nelts; i++)
1359 name = APR_ARRAY_IDX(revprops, i, char *);
1360 if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1361 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1362 APR_HASH_KEY_STRING, author);
1363 if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1364 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
1365 APR_HASH_KEY_STRING, date);
1366 if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1367 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
1368 APR_HASH_KEY_STRING, message);
1371 SVN_ERR(receiver(receiver_baton, log_entry, subpool));
1373 svn_pool_clear(subpool);
1375 svn_pool_destroy(subpool);
1377 /* Read the response. */
1378 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1380 return SVN_NO_ERROR;
1384 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1385 const char *path, svn_revnum_t rev,
1386 svn_node_kind_t *kind, apr_pool_t *pool)
1388 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1389 svn_ra_svn_conn_t *conn = sess_baton->conn;
1390 const char *kind_word;
1392 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev));
1393 SVN_ERR(handle_auth_request(sess_baton, pool));
1394 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word));
1395 SVN_ERR(interpret_kind(kind_word, pool, kind));
1396 return SVN_NO_ERROR;
1400 /* If ERR is a command not supported error, wrap it in a
1401 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1402 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1403 const char *msg)
1405 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1406 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1407 msg);
1408 return err;
1412 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1413 const char *path, svn_revnum_t rev,
1414 svn_dirent_t **dirent, apr_pool_t *pool)
1416 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1417 svn_ra_svn_conn_t *conn = sess_baton->conn;
1418 apr_array_header_t *list = NULL;
1419 const char *kind, *cdate, *cauthor;
1420 svn_revnum_t crev;
1421 svn_boolean_t has_props;
1422 apr_uint64_t size;
1423 svn_dirent_t *the_dirent;
1425 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "stat", "c(?r)", path, rev));
1427 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1428 _("'stat' not implemented")));
1430 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list));
1432 if (! list)
1434 *dirent = NULL;
1436 else
1438 SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "wnbr(?c)(?c)",
1439 &kind, &size, &has_props,
1440 &crev, &cdate, &cauthor));
1442 the_dirent = apr_palloc(pool, sizeof(*the_dirent));
1443 SVN_ERR(interpret_kind(kind, pool, &the_dirent->kind));
1444 the_dirent->size = size;/* FIXME: svn_filesize_t */
1445 the_dirent->has_props = has_props;
1446 the_dirent->created_rev = crev;
1447 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1448 the_dirent->last_author = cauthor;
1450 *dirent = the_dirent;
1453 return SVN_NO_ERROR;
1457 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1458 apr_hash_t **locations,
1459 const char *path,
1460 svn_revnum_t peg_revision,
1461 apr_array_header_t *location_revisions,
1462 apr_pool_t *pool)
1464 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1465 svn_ra_svn_conn_t *conn = sess_baton->conn;
1466 svn_revnum_t revision;
1467 svn_ra_svn_item_t *item;
1468 svn_boolean_t is_done;
1469 int i;
1470 const char *ret_path;
1472 /* Transmit the parameters. */
1473 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cr(!",
1474 "get-locations", path, peg_revision));
1475 for (i = 0; i < location_revisions->nelts; i++)
1477 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1478 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!r!", revision));
1481 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1483 /* Servers before 1.1 don't support this command. Check for this here. */
1484 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1485 _("'get-locations' not implemented")));
1487 /* Read the hash items. */
1488 is_done = FALSE;
1489 *locations = apr_hash_make(pool);
1490 while (!is_done)
1492 SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
1493 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1494 is_done = 1;
1495 else if (item->kind != SVN_RA_SVN_LIST)
1496 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1497 _("Location entry not a list"));
1498 else
1500 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "rc",
1501 &revision, &ret_path));
1502 ret_path = svn_path_canonicalize(ret_path, pool);
1503 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1504 sizeof(revision)),
1505 sizeof(revision), ret_path);
1509 /* Read the response. This is so the server would have a chance to
1510 * report an error. */
1511 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1513 return SVN_NO_ERROR;
1516 static svn_error_t *
1517 ra_svn_get_location_segments(svn_ra_session_t *session,
1518 const char *path,
1519 svn_revnum_t peg_revision,
1520 svn_revnum_t start_rev,
1521 svn_revnum_t end_rev,
1522 svn_location_segment_receiver_t receiver,
1523 void *receiver_baton,
1524 apr_pool_t *pool)
1526 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1527 svn_ra_svn_conn_t *conn = sess_baton->conn;
1528 svn_ra_svn_item_t *item;
1529 svn_boolean_t is_done;
1530 svn_revnum_t range_start, range_end;
1531 const char *ret_path;
1532 svn_location_segment_t *segment;
1533 apr_pool_t *subpool = svn_pool_create(pool);
1535 /* Transmit the parameters. */
1536 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
1537 "get-location-segments",
1538 path, peg_revision, start_rev, end_rev));
1540 /* Servers before 1.1 don't support this command. Check for this here. */
1541 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1542 _("'get-location-segments' not implemented")));
1544 /* Parse the response. */
1545 is_done = FALSE;
1546 while (!is_done)
1548 svn_pool_clear(subpool);
1549 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
1550 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1551 is_done = 1;
1552 else if (item->kind != SVN_RA_SVN_LIST)
1553 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1554 _("Location segment entry not a list"));
1555 else
1557 segment = apr_pcalloc(subpool, sizeof(*segment));
1558 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "rr(?c)",
1559 &range_start, &range_end, &ret_path));
1560 if (! (SVN_IS_VALID_REVNUM(range_start)
1561 && SVN_IS_VALID_REVNUM(range_end)))
1562 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1563 _("Expected valid revision range"));
1564 if (ret_path)
1565 ret_path = svn_path_canonicalize(ret_path, subpool);
1566 segment->path = ret_path;
1567 segment->range_start = range_start;
1568 segment->range_end = range_end;
1569 SVN_ERR(receiver(segment, receiver_baton, subpool));
1572 svn_pool_destroy(subpool);
1574 /* Read the response. This is so the server would have a chance to
1575 * report an error. */
1576 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1578 return SVN_NO_ERROR;
1581 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
1582 const char *path,
1583 svn_revnum_t start, svn_revnum_t end,
1584 svn_boolean_t include_merged_revisions,
1585 svn_file_rev_handler_t handler,
1586 void *handler_baton, apr_pool_t *pool)
1588 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1589 apr_pool_t *rev_pool, *chunk_pool;
1590 svn_ra_svn_item_t *item;
1591 const char *p;
1592 svn_revnum_t rev;
1593 apr_array_header_t *rev_proplist, *proplist;
1594 apr_hash_t *rev_props;
1595 apr_array_header_t *props;
1596 svn_boolean_t has_txdelta;
1597 svn_boolean_t had_revision = FALSE;
1598 svn_stream_t *stream;
1599 svn_txdelta_window_handler_t d_handler;
1600 void *d_baton;
1601 apr_size_t size;
1602 apr_uint64_t merged_rev_param;
1603 svn_boolean_t merged_rev;
1605 /* One sub-pool for each revision and one for each txdelta chunk.
1606 Note that the rev_pool must live during the following txdelta. */
1607 rev_pool = svn_pool_create(pool);
1608 chunk_pool = svn_pool_create(pool);
1610 SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs",
1611 "c(?r)(?r)b", path, start, end,
1612 include_merged_revisions));
1614 /* Servers before 1.1 don't support this command. Check for this here. */
1615 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1616 _("'get-file-revs' not implemented")));
1618 while (1)
1620 svn_pool_clear(rev_pool);
1621 svn_pool_clear(chunk_pool);
1622 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, rev_pool, &item));
1623 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1624 break;
1625 /* Either we've got a correct revision or we will error out below. */
1626 had_revision = TRUE;
1627 if (item->kind != SVN_RA_SVN_LIST)
1628 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1629 _("Revision entry not a list"));
1631 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, rev_pool,
1632 "crll?B", &p, &rev, &rev_proplist,
1633 &proplist, &merged_rev_param));
1634 p = svn_path_canonicalize(p, rev_pool);
1635 SVN_ERR(svn_ra_svn_parse_proplist(rev_proplist, rev_pool, &rev_props));
1636 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
1637 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1638 merged_rev = FALSE;
1639 else
1640 merged_rev = (svn_boolean_t) merged_rev_param;
1642 /* Get the first delta chunk so we know if there is a delta. */
1643 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item));
1644 if (item->kind != SVN_RA_SVN_STRING)
1645 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1646 _("Text delta chunk not a string"));
1647 has_txdelta = item->u.string->len > 0;
1649 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
1650 has_txdelta ? &d_handler : NULL, &d_baton,
1651 props, rev_pool));
1653 /* Process the text delta if any. */
1654 if (has_txdelta)
1656 if (d_handler)
1657 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
1658 rev_pool);
1659 else
1660 stream = NULL;
1661 while (item->u.string->len > 0)
1663 size = item->u.string->len;
1664 if (stream)
1665 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
1666 svn_pool_clear(chunk_pool);
1668 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item));
1669 if (item->kind != SVN_RA_SVN_STRING)
1670 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1671 _("Text delta chunk not a string"));
1673 if (stream)
1674 SVN_ERR(svn_stream_close(stream));
1678 SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton->conn, pool, ""));
1680 /* Return error if we didn't get any revisions. */
1681 if (!had_revision)
1682 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1683 _("The get-file-revs command didn't return "
1684 "any revisions"));
1686 svn_pool_destroy(chunk_pool);
1687 svn_pool_destroy(rev_pool);
1689 return SVN_NO_ERROR;
1692 /* For each path in PATH_REVS, send a 'lock' command to the server.
1693 Used with 1.2.x series servers which support locking, but of only
1694 one path at a time. ra_svn_lock(), which supports 'lock-many'
1695 is now the default. See svn_ra_lock() docstring for interface details. */
1696 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
1697 apr_hash_t *path_revs,
1698 const char *comment,
1699 svn_boolean_t steal_lock,
1700 svn_ra_lock_callback_t lock_func,
1701 void *lock_baton,
1702 apr_pool_t *pool)
1704 svn_ra_svn__session_baton_t *sess = session->priv;
1705 svn_ra_svn_conn_t* conn = sess->conn;
1706 apr_array_header_t *list;
1707 apr_hash_index_t *hi;
1708 apr_pool_t *iterpool = svn_pool_create(pool);
1710 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1712 svn_lock_t *lock;
1713 const void *key;
1714 const char *path;
1715 void *val;
1716 svn_revnum_t *revnum;
1717 svn_error_t *err, *callback_err = NULL;
1719 svn_pool_clear(iterpool);
1721 apr_hash_this(hi, &key, NULL, &val);
1722 path = key;
1723 revnum = val;
1725 SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "lock", "c(?c)b(?r)",
1726 path, comment,
1727 steal_lock, *revnum));
1729 /* Servers before 1.2 doesn't support locking. Check this here. */
1730 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
1731 _("Server doesn't support "
1732 "the lock command")));
1734 err = svn_ra_svn_read_cmd_response(conn, iterpool, "l", &list);
1736 if (!err)
1737 SVN_ERR(parse_lock(list, iterpool, &lock));
1739 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1740 return err;
1742 if (lock_func)
1743 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1744 err, iterpool);
1746 svn_error_clear(err);
1748 if (callback_err)
1749 return callback_err;
1752 svn_pool_destroy(iterpool);
1754 return SVN_NO_ERROR;
1757 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
1758 Used with 1.2.x series servers which support unlocking, but of only
1759 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
1760 now the default. See svn_ra_unlock() docstring for interface details. */
1761 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
1762 apr_hash_t *path_tokens,
1763 svn_boolean_t break_lock,
1764 svn_ra_lock_callback_t lock_func,
1765 void *lock_baton,
1766 apr_pool_t *pool)
1768 svn_ra_svn__session_baton_t *sess = session->priv;
1769 svn_ra_svn_conn_t* conn = sess->conn;
1770 apr_hash_index_t *hi;
1771 apr_pool_t *iterpool = svn_pool_create(pool);
1773 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1775 const void *key;
1776 const char *path;
1777 void *val;
1778 const char *token;
1779 svn_error_t *err, *callback_err = NULL;
1781 svn_pool_clear(iterpool);
1783 apr_hash_this(hi, &key, NULL, &val);
1784 path = key;
1785 if (strcmp(val, "") != 0)
1786 token = val;
1787 else
1788 token = NULL;
1790 SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "unlock", "c(?c)b",
1791 path, token, break_lock));
1793 /* Servers before 1.2 don't support locking. Check this here. */
1794 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
1795 _("Server doesn't support the unlock "
1796 "command")));
1798 err = svn_ra_svn_read_cmd_response(conn, iterpool, "");
1800 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1801 return err;
1803 if (lock_func)
1804 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
1806 svn_error_clear(err);
1808 if (callback_err)
1809 return callback_err;
1812 svn_pool_destroy(iterpool);
1814 return SVN_NO_ERROR;
1817 /* Tell the server to lock all paths in PATH_REVS.
1818 See svn_ra_lock() for interface details. */
1819 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
1820 apr_hash_t *path_revs,
1821 const char *comment,
1822 svn_boolean_t steal_lock,
1823 svn_ra_lock_callback_t lock_func,
1824 void *lock_baton,
1825 apr_pool_t *pool)
1827 svn_ra_svn__session_baton_t *sess = session->priv;
1828 svn_ra_svn_conn_t *conn = sess->conn;
1829 apr_hash_index_t *hi;
1830 svn_ra_svn_item_t *elt;
1831 svn_error_t *err, *callback_err = SVN_NO_ERROR;
1832 apr_pool_t *subpool = svn_pool_create(pool);
1833 const char *status;
1834 svn_lock_t *lock;
1835 apr_array_header_t *list = NULL;
1837 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)b(!", "lock-many",
1838 comment, steal_lock));
1840 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1842 const void *key;
1843 const char *path;
1844 void *val;
1845 svn_revnum_t *revnum;
1847 svn_pool_clear(subpool);
1848 apr_hash_this(hi, &key, NULL, &val);
1849 path = key;
1850 revnum = val;
1852 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?r)", path, *revnum));
1855 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1857 err = handle_auth_request(sess, pool);
1859 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
1860 * to 'lock'. */
1861 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1863 svn_error_clear(err);
1864 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
1865 lock_func, lock_baton, pool);
1868 if (err)
1869 return err;
1871 /* Loop over responses to get lock information. */
1872 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1874 const void *key;
1875 const char *path;
1877 apr_hash_this(hi, &key, NULL, NULL);
1878 path = key;
1880 svn_pool_clear(subpool);
1881 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &elt));
1883 /* The server might have encountered some sort of fatal error in
1884 the middle of the request list. If this happens, it will
1885 transmit "done" to end the lock-info early, and then the
1886 overall command response will talk about the fatal error. */
1887 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
1888 break;
1890 if (elt->kind != SVN_RA_SVN_LIST)
1891 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1892 _("Lock response not a list"));
1894 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "wl", &status,
1895 &list));
1897 if (strcmp(status, "failure") == 0)
1898 err = svn_ra_svn__handle_failure_status(list, subpool);
1899 else if (strcmp(status, "success") == 0)
1901 SVN_ERR(parse_lock(list, subpool, &lock));
1902 err = NULL;
1904 else
1905 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1906 _("Unknown status for lock command"));
1908 if (lock_func)
1909 callback_err = lock_func(lock_baton, path, TRUE,
1910 err ? NULL : lock,
1911 err, subpool);
1912 else
1913 callback_err = SVN_NO_ERROR;
1915 svn_error_clear(err);
1917 if (callback_err)
1918 return callback_err;
1921 /* If we didn't break early above, and the whole hash was traversed,
1922 read the final "done" from the server. */
1923 if (!hi)
1925 SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt));
1926 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
1927 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1928 _("Didn't receive end marker for lock "
1929 "responses"));
1932 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1934 svn_pool_destroy(subpool);
1936 return SVN_NO_ERROR;
1939 /* Tell the server to unlock all paths in PATH_TOKENS.
1940 See svn_ra_unlock() for interface details. */
1941 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
1942 apr_hash_t *path_tokens,
1943 svn_boolean_t break_lock,
1944 svn_ra_lock_callback_t lock_func,
1945 void *lock_baton,
1946 apr_pool_t *pool)
1948 svn_ra_svn__session_baton_t *sess = session->priv;
1949 svn_ra_svn_conn_t *conn = sess->conn;
1950 apr_hash_index_t *hi;
1951 apr_pool_t *subpool = svn_pool_create(pool);
1952 svn_error_t *err, *callback_err = NULL;
1953 svn_ra_svn_item_t *elt;
1954 const char *status = NULL;
1955 apr_array_header_t *list = NULL;
1956 const void *key;
1957 const char *path;
1959 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(b(!", "unlock-many",
1960 break_lock));
1962 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1964 void *val;
1965 const char *token;
1967 svn_pool_clear(subpool);
1968 apr_hash_this(hi, &key, NULL, &val);
1969 path = key;
1971 if (strcmp(val, "") != 0)
1972 token = val;
1973 else
1974 token = NULL;
1976 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?c)", path, token));
1979 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1981 err = handle_auth_request(sess, pool);
1983 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
1984 * to 'unlock'.
1986 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1988 svn_error_clear(err);
1989 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
1990 lock_baton, pool);
1993 if (err)
1994 return err;
1996 /* Loop over responses to unlock files. */
1997 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1999 svn_pool_clear(subpool);
2001 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &elt));
2003 /* The server might have encountered some sort of fatal error in
2004 the middle of the request list. If this happens, it will
2005 transmit "done" to end the lock-info early, and then the
2006 overall command response will talk about the fatal error. */
2007 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2008 break;
2010 apr_hash_this(hi, &key, NULL, NULL);
2011 path = key;
2013 if (elt->kind != SVN_RA_SVN_LIST)
2014 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2015 _("Unlock response not a list"));
2017 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "wl", &status,
2018 &list));
2020 if (strcmp(status, "failure") == 0)
2021 err = svn_ra_svn__handle_failure_status(list, subpool);
2022 else if (strcmp(status, "success") == 0)
2024 SVN_ERR(svn_ra_svn_parse_tuple(list, subpool, "c", &path));
2025 err = SVN_NO_ERROR;
2027 else
2028 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2029 _("Unknown status for unlock command"));
2031 if (lock_func)
2032 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2033 subpool);
2034 else
2035 callback_err = SVN_NO_ERROR;
2037 svn_error_clear(err);
2039 if (callback_err)
2040 return callback_err;
2043 /* If we didn't break early above, and the whole hash was traversed,
2044 read the final "done" from the server. */
2045 if (!hi)
2047 SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt));
2048 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2049 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2050 _("Didn't receive end marker for unlock "
2051 "responses"));
2054 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
2056 svn_pool_destroy(subpool);
2058 return SVN_NO_ERROR;
2061 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2062 svn_lock_t **lock,
2063 const char *path,
2064 apr_pool_t *pool)
2066 svn_ra_svn__session_baton_t *sess = session->priv;
2067 svn_ra_svn_conn_t* conn = sess->conn;
2068 apr_array_header_t *list;
2070 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-lock", "c", path));
2072 /* Servers before 1.2 doesn't support locking. Check this here. */
2073 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2074 _("Server doesn't support the get-lock "
2075 "command")));
2077 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list));
2078 if (list)
2079 SVN_ERR(parse_lock(list, pool, lock));
2080 else
2081 *lock = NULL;
2083 return SVN_NO_ERROR;
2086 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2087 apr_hash_t **locks,
2088 const char *path,
2089 apr_pool_t *pool)
2091 svn_ra_svn__session_baton_t *sess = session->priv;
2092 svn_ra_svn_conn_t* conn = sess->conn;
2093 apr_array_header_t *list;
2094 int i;
2095 svn_ra_svn_item_t *elt;
2096 svn_lock_t *lock;
2098 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-locks", "c", path));
2100 /* Servers before 1.2 doesn't support locking. Check this here. */
2101 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2102 _("Server doesn't support the get-lock "
2103 "command")));
2105 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &list));
2107 *locks = apr_hash_make(pool);
2109 for (i = 0; i < list->nelts; ++i)
2111 elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2113 if (elt->kind != SVN_RA_SVN_LIST)
2114 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2115 _("Lock element not a list"));
2116 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2117 apr_hash_set(*locks, lock->path, APR_HASH_KEY_STRING, lock);
2120 return SVN_NO_ERROR;
2124 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2125 svn_revnum_t revision,
2126 svn_revnum_t low_water_mark,
2127 svn_boolean_t send_deltas,
2128 const svn_delta_editor_t *editor,
2129 void *edit_baton,
2130 apr_pool_t *pool)
2132 svn_ra_svn__session_baton_t *sess = session->priv;
2134 SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay", "rrb", revision,
2135 low_water_mark, send_deltas));
2137 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2138 _("Server doesn't support the replay "
2139 "command")));
2141 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2142 NULL, TRUE));
2144 SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
2146 return SVN_NO_ERROR;
2150 static svn_error_t *
2151 ra_svn_replay_range(svn_ra_session_t *session,
2152 svn_revnum_t start_revision,
2153 svn_revnum_t end_revision,
2154 svn_revnum_t low_water_mark,
2155 svn_boolean_t send_deltas,
2156 svn_ra_replay_revstart_callback_t revstart_func,
2157 svn_ra_replay_revfinish_callback_t revfinish_func,
2158 void *replay_baton,
2159 apr_pool_t *pool)
2161 svn_ra_svn__session_baton_t *sess = session->priv;
2162 apr_pool_t *iterpool;
2163 svn_revnum_t rev;
2165 SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay-range", "rrrb",
2166 start_revision, end_revision,
2167 low_water_mark, send_deltas));
2169 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2170 _("Server doesn't support the replay-range "
2171 "command")));
2173 iterpool = svn_pool_create(pool);
2174 for (rev = start_revision; rev <= end_revision; rev++)
2176 const svn_delta_editor_t *editor;
2177 void *edit_baton;
2178 apr_hash_t *rev_props;
2179 svn_ra_svn_item_t *item;
2181 svn_pool_clear(iterpool);
2183 SVN_ERR(svn_ra_svn_read_item(sess->conn, iterpool, &item));
2184 if (item->kind != SVN_RA_SVN_LIST)
2185 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2186 _("Revision properties not a list"));
2188 SVN_ERR(svn_ra_svn_parse_proplist(item->u.list, iterpool, &rev_props));
2190 SVN_ERR(revstart_func(rev, replay_baton,
2191 &editor, &edit_baton,
2192 rev_props,
2193 iterpool));
2194 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2195 editor, edit_baton,
2196 NULL, TRUE));
2197 SVN_ERR(revfinish_func(rev, replay_baton,
2198 editor, edit_baton,
2199 rev_props,
2200 iterpool));
2202 svn_pool_destroy(iterpool);
2204 SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
2206 return SVN_NO_ERROR;
2210 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
2211 svn_boolean_t *has,
2212 const char *capability,
2213 apr_pool_t *pool)
2215 svn_ra_svn__session_baton_t *sess = session->priv;
2217 *has = FALSE;
2219 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0)
2220 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_DEPTH);
2221 else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
2222 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_MERGEINFO);
2223 else if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0)
2224 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_LOG_REVPROPS);
2225 else if (strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0)
2226 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_PARTIAL_REPLAY);
2227 else /* Don't know any other capabilities, so error. */
2229 return svn_error_createf
2230 (SVN_ERR_RA_UNKNOWN_CAPABILITY, NULL,
2231 _("Don't know anything about capability '%s'"), capability);
2234 return SVN_NO_ERROR;
2238 static const svn_ra__vtable_t ra_svn_vtable = {
2239 svn_ra_svn_version,
2240 ra_svn_get_description,
2241 ra_svn_get_schemes,
2242 ra_svn_open,
2243 ra_svn_reparent,
2244 ra_svn_get_session_url,
2245 ra_svn_get_latest_rev,
2246 ra_svn_get_dated_rev,
2247 ra_svn_change_rev_prop,
2248 ra_svn_rev_proplist,
2249 ra_svn_rev_prop,
2250 ra_svn_commit,
2251 ra_svn_get_file,
2252 ra_svn_get_dir,
2253 ra_svn_get_mergeinfo,
2254 ra_svn_update,
2255 ra_svn_switch,
2256 ra_svn_status,
2257 ra_svn_diff,
2258 ra_svn_log,
2259 ra_svn_check_path,
2260 ra_svn_stat,
2261 ra_svn_get_uuid,
2262 ra_svn_get_repos_root,
2263 ra_svn_get_locations,
2264 ra_svn_get_location_segments,
2265 ra_svn_get_file_revs,
2266 ra_svn_lock,
2267 ra_svn_unlock,
2268 ra_svn_get_lock,
2269 ra_svn_get_locks,
2270 ra_svn_replay,
2271 ra_svn_has_capability,
2272 ra_svn_replay_range,
2275 svn_error_t *
2276 svn_ra_svn__init(const svn_version_t *loader_version,
2277 const svn_ra__vtable_t **vtable,
2278 apr_pool_t *pool)
2280 static const svn_version_checklist_t checklist[] =
2282 { "svn_subr", svn_subr_version },
2283 { "svn_delta", svn_delta_version },
2284 { NULL, NULL }
2287 SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist));
2289 /* Simplified version check to make sure we can safely use the
2290 VTABLE parameter. The RA loader does a more exhaustive check. */
2291 if (loader_version->major != SVN_VER_MAJOR)
2293 return svn_error_createf
2294 (SVN_ERR_VERSION_MISMATCH, NULL,
2295 _("Unsupported RA loader version (%d) for ra_svn"),
2296 loader_version->major);
2299 *vtable = &ra_svn_vtable;
2301 #ifdef SVN_HAVE_SASL
2302 SVN_ERR(svn_ra_svn__sasl_init());
2303 #endif
2305 return SVN_NO_ERROR;
2308 /* Compatibility wrapper for the 1.1 and before API. */
2309 #define NAME "ra_svn"
2310 #define DESCRIPTION RA_SVN_DESCRIPTION
2311 #define VTBL ra_svn_vtable
2312 #define INITFUNC svn_ra_svn__init
2313 #define COMPAT_INITFUNC svn_ra_svn_init
2314 #include "../libsvn_ra/wrapper_template.h"