Followup to r29625: fix getopt tests.
[svn.git] / subversion / libsvn_ra_svn / client.c
blobdf55e25450cee6da6ca5581347ee7ca4cddfa70d
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, *server_caplist, *repos_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, &server_caplist));
552 /* We support protocol version 2. */
553 if (minver > 2)
554 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
555 _("Server requires minimum version %d"),
556 (int) minver);
557 if (maxver < 2)
558 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
559 _("Server only supports versions up to %d"),
560 (int) maxver);
561 SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
563 /* All released versions of Subversion support edit-pipeline,
564 * so we do not support servers that do not. */
565 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
566 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
567 _("Server does not support edit pipelining"));
569 /* In protocol version 2, we send back our protocol version, our
570 * capability list, and the URL, and subsequently there is an auth
571 * request. */
572 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(wwwwww)c", (apr_uint64_t) 2,
573 SVN_RA_SVN_CAP_EDIT_PIPELINE,
574 SVN_RA_SVN_CAP_SVNDIFF1,
575 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
576 SVN_RA_SVN_CAP_DEPTH,
577 SVN_RA_SVN_CAP_MERGEINFO,
578 SVN_RA_SVN_CAP_LOG_REVPROPS,
579 url));
580 SVN_ERR(handle_auth_request(sess, pool));
582 /* This is where the security layer would go into effect if we
583 * supported security layers, which is a ways off. */
585 /* Read the repository's uuid and root URL, and perhaps learn more
586 capabilities that weren't available before now. */
587 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
588 &conn->repos_root, &repos_caplist));
589 if (repos_caplist)
590 SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
592 if (conn->repos_root)
594 conn->repos_root = svn_path_canonicalize(conn->repos_root, pool);
595 /* We should check that the returned string is a prefix of url, since
596 that's the API guarantee, but this isn't true for 1.0 servers.
597 Checking the length prevents client crashes. */
598 if (strlen(conn->repos_root) > strlen(url))
599 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
600 _("Impossibly long repository root from "
601 "server"));
604 *sess_p = sess;
606 return SVN_NO_ERROR;
610 #ifdef SVN_HAVE_SASL
611 #define RA_SVN_DESCRIPTION \
612 N_("Module for accessing a repository using the svn network protocol.\n" \
613 " - with Cyrus SASL authentication")
614 #else
615 #define RA_SVN_DESCRIPTION \
616 N_("Module for accessing a repository using the svn network protocol.")
617 #endif
619 static const char *ra_svn_get_description(void)
621 return _(RA_SVN_DESCRIPTION);
624 static const char * const *
625 ra_svn_get_schemes(apr_pool_t *pool)
627 static const char *schemes[] = { "svn", NULL };
629 return schemes;
634 static svn_error_t *ra_svn_open(svn_ra_session_t *session, const char *url,
635 const svn_ra_callbacks2_t *callbacks,
636 void *callback_baton,
637 apr_hash_t *config,
638 apr_pool_t *pool)
640 apr_pool_t *sess_pool = svn_pool_create(pool);
641 svn_ra_svn__session_baton_t *sess;
642 const char *tunnel, **tunnel_argv;
643 apr_uri_t uri;
645 SVN_ERR(parse_url(url, &uri, sess_pool));
647 parse_tunnel(url, &tunnel, pool);
649 if (tunnel)
650 SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
651 pool));
652 else
653 tunnel_argv = NULL;
655 /* We open the session in a subpool so we can get rid of it if we
656 reparent with a server that doesn't support reparenting. */
657 SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
658 callbacks, callback_baton, sess_pool));
659 session->priv = sess;
661 return SVN_NO_ERROR;
664 static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
665 const char *url,
666 apr_pool_t *pool)
668 svn_ra_svn__session_baton_t *sess = ra_session->priv;
669 svn_ra_svn_conn_t *conn = sess->conn;
670 svn_error_t *err;
671 apr_pool_t *sess_pool;
672 svn_ra_svn__session_baton_t *new_sess;
673 apr_uri_t uri;
675 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "reparent", "c", url));
676 err = handle_auth_request(sess, pool);
677 if (! err)
679 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
680 sess->url = apr_pstrdup(sess->pool, url);
681 return SVN_NO_ERROR;
683 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
684 return err;
686 /* Servers before 1.4 doesn't support this command; try to reconnect
687 instead. */
688 svn_error_clear(err);
689 /* Create a new subpool of the RA session pool. */
690 sess_pool = svn_pool_create(ra_session->pool);
691 err = parse_url(url, &uri, sess_pool);
692 if (! err)
693 err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
694 sess->callbacks, sess->callbacks_baton, sess_pool);
695 /* We destroy the new session pool on error, since it is allocated in
696 the main session pool. */
697 if (err)
699 svn_pool_destroy(sess_pool);
700 return err;
703 /* We have a new connection, assign it and destroy the old. */
704 ra_session->priv = new_sess;
705 svn_pool_destroy(sess->pool);
707 return SVN_NO_ERROR;
710 static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
711 const char **url, apr_pool_t *pool)
713 svn_ra_svn__session_baton_t *sess = session->priv;
714 *url = apr_pstrdup(pool, sess->url);
715 return SVN_NO_ERROR;
718 static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
719 svn_revnum_t *rev, apr_pool_t *pool)
721 svn_ra_svn__session_baton_t *sess_baton = session->priv;
722 svn_ra_svn_conn_t *conn = sess_baton->conn;
724 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-latest-rev", ""));
725 SVN_ERR(handle_auth_request(sess_baton, pool));
726 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
727 return SVN_NO_ERROR;
730 static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
731 svn_revnum_t *rev, apr_time_t tm,
732 apr_pool_t *pool)
734 svn_ra_svn__session_baton_t *sess_baton = session->priv;
735 svn_ra_svn_conn_t *conn = sess_baton->conn;
737 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-dated-rev", "c",
738 svn_time_to_cstring(tm, pool)));
739 SVN_ERR(handle_auth_request(sess_baton, pool));
740 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
741 return SVN_NO_ERROR;
744 static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
745 const char *name,
746 const svn_string_t *value,
747 apr_pool_t *pool)
749 svn_ra_svn__session_baton_t *sess_baton = session->priv;
750 svn_ra_svn_conn_t *conn = sess_baton->conn;
752 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop", "rc?s",
753 rev, name, value));
754 SVN_ERR(handle_auth_request(sess_baton, pool));
755 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
756 return SVN_NO_ERROR;
759 static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
760 apr_pool_t *pool)
762 svn_ra_svn__session_baton_t *sess_baton = session->priv;
763 svn_ra_svn_conn_t *conn = sess_baton->conn;
765 *uuid = conn->uuid;
766 return SVN_NO_ERROR;
769 static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
770 apr_pool_t *pool)
772 svn_ra_svn__session_baton_t *sess_baton = session->priv;
773 svn_ra_svn_conn_t *conn = sess_baton->conn;
775 if (!conn->repos_root)
776 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
777 _("Server did not send repository root"));
778 *url = conn->repos_root;
779 return SVN_NO_ERROR;
782 static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
783 apr_hash_t **props, apr_pool_t *pool)
785 svn_ra_svn__session_baton_t *sess_baton = session->priv;
786 svn_ra_svn_conn_t *conn = sess_baton->conn;
787 apr_array_header_t *proplist;
789 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-proplist", "r", rev));
790 SVN_ERR(handle_auth_request(sess_baton, pool));
791 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &proplist));
792 SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
793 return SVN_NO_ERROR;
796 static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
797 const char *name,
798 svn_string_t **value, apr_pool_t *pool)
800 svn_ra_svn__session_baton_t *sess_baton = session->priv;
801 svn_ra_svn_conn_t *conn = sess_baton->conn;
803 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-prop", "rc", rev, name));
804 SVN_ERR(handle_auth_request(sess_baton, pool));
805 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?s)", value));
806 return SVN_NO_ERROR;
809 static svn_error_t *ra_svn_end_commit(void *baton)
811 ra_svn_commit_callback_baton_t *ccb = baton;
812 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
814 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
815 SVN_ERR(svn_ra_svn_read_tuple(ccb->sess_baton->conn, ccb->pool,
816 "r(?c)(?c)?(?c)",
817 &(commit_info->revision),
818 &(commit_info->date),
819 &(commit_info->author),
820 &(commit_info->post_commit_err)));
822 return ccb->callback(commit_info, ccb->callback_baton, ccb->pool);
826 static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
827 const svn_delta_editor_t **editor,
828 void **edit_baton,
829 apr_hash_t *revprop_table,
830 svn_commit_callback2_t callback,
831 void *callback_baton,
832 apr_hash_t *lock_tokens,
833 svn_boolean_t keep_locks,
834 apr_pool_t *pool)
836 svn_ra_svn__session_baton_t *sess_baton = session->priv;
837 svn_ra_svn_conn_t *conn = sess_baton->conn;
838 ra_svn_commit_callback_baton_t *ccb;
839 apr_hash_index_t *hi;
840 apr_pool_t *iterpool;
841 const svn_string_t *log_msg = apr_hash_get(revprop_table,
842 SVN_PROP_REVISION_LOG,
843 APR_HASH_KEY_STRING);
845 /* If we're sending revprops other than svn:log, make sure the server won't
846 silently ignore them. */
847 if (apr_hash_count(revprop_table) > 1 &&
848 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
849 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
850 _("Server doesn't support setting arbitrary "
851 "revision properties during commit"));
853 /* Tell the server we're starting the commit.
854 Send log message here for backwards compatibility with servers
855 before 1.5. */
856 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(!", "commit",
857 log_msg->data));
858 if (lock_tokens)
860 iterpool = svn_pool_create(pool);
861 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
863 const void *key;
864 void *val;
865 const char *path, *token;
866 svn_pool_clear(iterpool);
867 apr_hash_this(hi, &key, NULL, &val);
868 path = key;
869 token = val;
870 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cc", path, token));
872 svn_pool_destroy(iterpool);
874 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b(!", keep_locks));
875 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, revprop_table));
876 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
877 SVN_ERR(handle_auth_request(sess_baton, pool));
878 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
880 /* Remember a few arguments for when the commit is over. */
881 ccb = apr_palloc(pool, sizeof(*ccb));
882 ccb->sess_baton = sess_baton;
883 ccb->pool = pool;
884 ccb->callback = callback;
885 ccb->callback_baton = callback_baton;
887 /* Fetch an editor for the caller to drive. The editor will call
888 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
889 * in the new_rev, committed_date, and committed_author values. */
890 svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
891 ra_svn_end_commit, ccb);
892 return SVN_NO_ERROR;
895 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
896 svn_revnum_t rev, svn_stream_t *stream,
897 svn_revnum_t *fetched_rev,
898 apr_hash_t **props,
899 apr_pool_t *pool)
901 svn_ra_svn__session_baton_t *sess_baton = session->priv;
902 svn_ra_svn_conn_t *conn = sess_baton->conn;
903 apr_array_header_t *proplist;
904 unsigned char digest[APR_MD5_DIGESTSIZE];
905 const char *expected_checksum, *hex_digest;
906 apr_md5_ctx_t md5_context;
907 apr_pool_t *iterpool;
909 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-file", "c(?r)bb", path,
910 rev, (props != NULL), (stream != NULL)));
911 SVN_ERR(handle_auth_request(sess_baton, pool));
912 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?c)rl",
913 &expected_checksum,
914 &rev, &proplist));
916 if (fetched_rev)
917 *fetched_rev = rev;
918 if (props)
919 SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
921 /* We're done if the contents weren't wanted. */
922 if (!stream)
923 return SVN_NO_ERROR;
925 if (expected_checksum)
926 apr_md5_init(&md5_context);
928 /* Read the file's contents. */
929 iterpool = svn_pool_create(pool);
930 while (1)
932 svn_ra_svn_item_t *item;
934 svn_pool_clear(iterpool);
935 SVN_ERR(svn_ra_svn_read_item(conn, iterpool, &item));
936 if (item->kind != SVN_RA_SVN_STRING)
937 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
938 _("Non-string as part of file contents"));
939 if (item->u.string->len == 0)
940 break;
942 if (expected_checksum)
943 apr_md5_update(&md5_context, item->u.string->data,
944 item->u.string->len);
946 SVN_ERR(svn_stream_write(stream, item->u.string->data,
947 &item->u.string->len));
949 svn_pool_destroy(iterpool);
951 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
953 if (expected_checksum)
955 apr_md5_final(digest, &md5_context);
956 hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
957 if (strcmp(hex_digest, expected_checksum) != 0)
958 return svn_error_createf
959 (SVN_ERR_CHECKSUM_MISMATCH, NULL,
960 _("Checksum mismatch for '%s':\n"
961 " expected checksum: %s\n"
962 " actual checksum: %s\n"),
963 path, expected_checksum, hex_digest);
966 return SVN_NO_ERROR;
969 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
970 apr_hash_t **dirents,
971 svn_revnum_t *fetched_rev,
972 apr_hash_t **props,
973 const char *path,
974 svn_revnum_t rev,
975 apr_uint32_t dirent_fields,
976 apr_pool_t *pool)
978 svn_ra_svn__session_baton_t *sess_baton = session->priv;
979 svn_ra_svn_conn_t *conn = sess_baton->conn;
980 svn_revnum_t crev;
981 apr_array_header_t *proplist, *dirlist;
982 int i;
983 svn_ra_svn_item_t *elt;
984 const char *name, *kind, *cdate, *cauthor;
985 svn_boolean_t has_props;
986 apr_uint64_t size;
987 svn_dirent_t *dirent;
989 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
990 rev, (props != NULL), (dirents != NULL)));
991 if (dirent_fields & SVN_DIRENT_KIND)
993 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
995 if (dirent_fields & SVN_DIRENT_SIZE)
997 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
999 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1001 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1003 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1005 SVN_ERR(svn_ra_svn_write_word(conn, pool,
1006 SVN_RA_SVN_DIRENT_CREATED_REV));
1008 if (dirent_fields & SVN_DIRENT_TIME)
1010 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1012 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1014 SVN_ERR(svn_ra_svn_write_word(conn, pool,
1015 SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1017 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1019 SVN_ERR(handle_auth_request(sess_baton, pool));
1020 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "rll", &rev, &proplist,
1021 &dirlist));
1023 if (fetched_rev)
1024 *fetched_rev = rev;
1025 if (props)
1026 SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
1028 /* We're done if dirents aren't wanted. */
1029 if (!dirents)
1030 return SVN_NO_ERROR;
1032 /* Interpret the directory list. */
1033 *dirents = apr_hash_make(pool);
1034 for (i = 0; i < dirlist->nelts; i++)
1036 elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1037 if (elt->kind != SVN_RA_SVN_LIST)
1038 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1039 _("Dirlist element not a list"));
1040 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1041 &name, &kind, &size, &has_props,
1042 &crev, &cdate, &cauthor));
1043 name = svn_path_canonicalize(name, pool);
1044 dirent = apr_palloc(pool, sizeof(*dirent));
1045 SVN_ERR(interpret_kind(kind, pool, &dirent->kind));
1046 dirent->size = size;/* FIXME: svn_filesize_t */
1047 dirent->has_props = has_props;
1048 dirent->created_rev = crev;
1049 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1050 dirent->last_author = cauthor;
1051 apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, dirent);
1054 return SVN_NO_ERROR;
1057 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1058 server, which defaults to youngest. */
1059 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1060 svn_mergeinfo_catalog_t *catalog,
1061 const apr_array_header_t *paths,
1062 svn_revnum_t revision,
1063 svn_mergeinfo_inheritance_t inherit,
1064 svn_boolean_t include_descendants,
1065 apr_pool_t *pool)
1067 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1068 svn_ra_svn_conn_t *conn = sess_baton->conn;
1069 int i;
1070 apr_array_header_t *mergeinfo_tuple;
1071 svn_ra_svn_item_t *elt;
1072 const char *path, *to_parse;
1074 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1075 for (i = 0; i < paths->nelts; i++)
1077 path = APR_ARRAY_IDX(paths, i, const char *);
1078 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
1080 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)wb)", revision,
1081 svn_inheritance_to_word(inherit),
1082 include_descendants));
1084 SVN_ERR(handle_auth_request(sess_baton, pool));
1085 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1087 *catalog = NULL;
1088 if (mergeinfo_tuple->nelts > 0)
1090 *catalog = apr_hash_make(pool);
1091 for (i = 0; i < mergeinfo_tuple->nelts; i++)
1093 svn_mergeinfo_t for_path;
1095 elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1096 if (elt->kind != SVN_RA_SVN_LIST)
1097 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1098 _("Mergeinfo element is not a list"));
1099 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cc",
1100 &path, &to_parse));
1101 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1102 apr_hash_set(*catalog, path, APR_HASH_KEY_STRING, for_path);
1106 return SVN_NO_ERROR;
1109 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1110 const svn_ra_reporter3_t **reporter,
1111 void **report_baton, svn_revnum_t rev,
1112 const char *target, svn_depth_t depth,
1113 svn_boolean_t send_copyfrom_args,
1114 const svn_delta_editor_t *update_editor,
1115 void *update_baton, apr_pool_t *pool)
1117 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1118 svn_ra_svn_conn_t *conn = sess_baton->conn;
1119 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1121 /* Tell the server we want to start an update. */
1122 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cbwb", rev, target,
1123 recurse, svn_depth_to_word(depth),
1124 send_copyfrom_args));
1125 SVN_ERR(handle_auth_request(sess_baton, pool));
1127 /* Fetch a reporter for the caller to drive. The reporter will drive
1128 * update_editor upon finish_report(). */
1129 ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1130 target, depth, reporter, report_baton);
1131 return SVN_NO_ERROR;
1134 static svn_error_t *ra_svn_switch(svn_ra_session_t *session,
1135 const svn_ra_reporter3_t **reporter,
1136 void **report_baton, svn_revnum_t rev,
1137 const char *target, svn_depth_t depth,
1138 const char *switch_url,
1139 const svn_delta_editor_t *update_editor,
1140 void *update_baton, apr_pool_t *pool)
1142 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1143 svn_ra_svn_conn_t *conn = sess_baton->conn;
1144 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1146 /* Tell the server we want to start a switch. */
1147 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbcw", rev,
1148 target, recurse, switch_url,
1149 svn_depth_to_word(depth)));
1150 SVN_ERR(handle_auth_request(sess_baton, pool));
1152 /* Fetch a reporter for the caller to drive. The reporter will drive
1153 * update_editor upon finish_report(). */
1154 ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1155 target, depth, reporter, report_baton);
1156 return SVN_NO_ERROR;
1159 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1160 const svn_ra_reporter3_t **reporter,
1161 void **report_baton,
1162 const char *target, svn_revnum_t rev,
1163 svn_depth_t depth,
1164 const svn_delta_editor_t *status_editor,
1165 void *status_baton, apr_pool_t *pool)
1167 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1168 svn_ra_svn_conn_t *conn = sess_baton->conn;
1169 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1171 /* Tell the server we want to start a status operation. */
1172 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)w",
1173 target, recurse, rev,
1174 svn_depth_to_word(depth)));
1175 SVN_ERR(handle_auth_request(sess_baton, pool));
1177 /* Fetch a reporter for the caller to drive. The reporter will drive
1178 * status_editor upon finish_report(). */
1179 ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1180 target, depth, reporter, report_baton);
1181 return SVN_NO_ERROR;
1184 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1185 const svn_ra_reporter3_t **reporter,
1186 void **report_baton,
1187 svn_revnum_t rev, const char *target,
1188 svn_depth_t depth,
1189 svn_boolean_t ignore_ancestry,
1190 svn_boolean_t text_deltas,
1191 const char *versus_url,
1192 const svn_delta_editor_t *diff_editor,
1193 void *diff_baton, apr_pool_t *pool)
1195 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1196 svn_ra_svn_conn_t *conn = sess_baton->conn;
1197 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1199 /* Tell the server we want to start a diff. */
1200 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbcbw", rev,
1201 target, recurse, ignore_ancestry,
1202 versus_url, text_deltas,
1203 svn_depth_to_word(depth)));
1204 SVN_ERR(handle_auth_request(sess_baton, pool));
1206 /* Fetch a reporter for the caller to drive. The reporter will drive
1207 * diff_editor upon finish_report(). */
1208 ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1209 target, depth, reporter, report_baton);
1210 return SVN_NO_ERROR;
1213 static svn_error_t *ra_svn_log(svn_ra_session_t *session,
1214 const apr_array_header_t *paths,
1215 svn_revnum_t start, svn_revnum_t end,
1216 int limit,
1217 svn_boolean_t discover_changed_paths,
1218 svn_boolean_t strict_node_history,
1219 svn_boolean_t include_merged_revisions,
1220 const apr_array_header_t *revprops,
1221 svn_log_entry_receiver_t receiver,
1222 void *receiver_baton, apr_pool_t *pool)
1224 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1225 svn_ra_svn_conn_t *conn = sess_baton->conn;
1226 apr_pool_t *subpool;
1227 int i;
1228 const char *path, *cpath, *action, *copy_path;
1229 svn_string_t *author, *date, *message;
1230 svn_ra_svn_item_t *item, *elt;
1231 char *name;
1232 apr_array_header_t *cplist, *rplist;
1233 apr_hash_t *cphash;
1234 svn_revnum_t rev, copy_rev;
1235 svn_log_changed_path_t *change;
1236 int nreceived = 0;
1237 apr_uint64_t has_children_param, invalid_revnum_param;
1238 svn_boolean_t has_children;
1239 svn_log_entry_t *log_entry;
1240 svn_boolean_t want_custom_revprops;
1241 apr_uint64_t revprop_count;
1243 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log"));
1244 if (paths)
1246 for (i = 0; i < paths->nelts; i++)
1248 path = APR_ARRAY_IDX(paths, i, const char *);
1249 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path));
1252 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1253 discover_changed_paths, strict_node_history,
1254 (apr_uint64_t) limit,
1255 include_merged_revisions));
1256 if (revprops)
1258 want_custom_revprops = FALSE;
1259 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w(!", "revprops"));
1260 for (i = 0; i < revprops->nelts; i++)
1262 name = APR_ARRAY_IDX(revprops, i, char *);
1263 SVN_ERR(svn_ra_svn_write_cstring(conn, pool, name));
1264 if (!want_custom_revprops
1265 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
1266 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
1267 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
1268 want_custom_revprops = TRUE;
1270 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1272 else
1274 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!w())", "all-revprops"));
1275 want_custom_revprops = TRUE;
1278 SVN_ERR(handle_auth_request(sess_baton, pool));
1280 /* Read the log messages. */
1281 subpool = svn_pool_create(pool);
1282 while (1)
1284 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
1285 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1286 break;
1287 if (item->kind != SVN_RA_SVN_LIST)
1288 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1289 _("Log entry not a list"));
1290 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool,
1291 "lr(?s)(?s)(?s)?BBnl",
1292 &cplist, &rev, &author, &date,
1293 &message, &has_children_param,
1294 &invalid_revnum_param,
1295 &revprop_count, &rplist));
1296 if (want_custom_revprops && rplist == NULL)
1298 /* Caller asked for custom revprops, but server is too old. */
1299 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1300 _("Server does not support custom revprops"
1301 " via log"));
1304 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1305 has_children = FALSE;
1306 else
1307 has_children = (svn_boolean_t) has_children_param;
1309 /* Because the svn protocol won't let us send an invalid revnum, we have
1310 to recover that fact using the extra parameter. */
1311 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1312 && invalid_revnum_param == TRUE)
1313 rev = SVN_INVALID_REVNUM;
1315 if (cplist->nelts > 0)
1317 /* Interpret the changed-paths list. */
1318 cphash = apr_hash_make(subpool);
1319 for (i = 0; i < cplist->nelts; i++)
1321 elt = &APR_ARRAY_IDX(cplist, i, svn_ra_svn_item_t);
1322 if (elt->kind != SVN_RA_SVN_LIST)
1323 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1324 _("Changed-path entry not a list"));
1325 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "cw(?cr)",
1326 &cpath, &action, &copy_path,
1327 &copy_rev));
1328 cpath = svn_path_canonicalize(cpath, subpool);
1329 if (copy_path)
1330 copy_path = svn_path_canonicalize(copy_path, subpool);
1331 change = apr_palloc(subpool, sizeof(*change));
1332 change->action = *action;
1333 change->copyfrom_path = copy_path;
1334 change->copyfrom_rev = copy_rev;
1335 apr_hash_set(cphash, cpath, APR_HASH_KEY_STRING, change);
1338 else
1339 cphash = NULL;
1341 if (! (limit && ++nreceived > limit))
1343 log_entry = svn_log_entry_create(subpool);
1345 log_entry->changed_paths = cphash;
1346 log_entry->revision = rev;
1347 log_entry->has_children = has_children;
1348 if (rplist)
1349 SVN_ERR(svn_ra_svn_parse_proplist(rplist, pool,
1350 &log_entry->revprops));
1351 if (log_entry->revprops == NULL)
1352 log_entry->revprops = apr_hash_make(pool);
1353 if (revprops == NULL)
1355 /* Caller requested all revprops; set author/date/log. */
1356 if (author)
1357 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1358 APR_HASH_KEY_STRING, author);
1359 if (date)
1360 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
1361 APR_HASH_KEY_STRING, date);
1362 if (message)
1363 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
1364 APR_HASH_KEY_STRING, message);
1366 else
1368 /* Caller requested some; maybe set author/date/log. */
1369 for (i = 0; i < revprops->nelts; i++)
1371 name = APR_ARRAY_IDX(revprops, i, char *);
1372 if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1373 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1374 APR_HASH_KEY_STRING, author);
1375 if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1376 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
1377 APR_HASH_KEY_STRING, date);
1378 if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1379 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
1380 APR_HASH_KEY_STRING, message);
1383 SVN_ERR(receiver(receiver_baton, log_entry, subpool));
1385 svn_pool_clear(subpool);
1387 svn_pool_destroy(subpool);
1389 /* Read the response. */
1390 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1392 return SVN_NO_ERROR;
1396 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1397 const char *path, svn_revnum_t rev,
1398 svn_node_kind_t *kind, apr_pool_t *pool)
1400 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1401 svn_ra_svn_conn_t *conn = sess_baton->conn;
1402 const char *kind_word;
1404 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev));
1405 SVN_ERR(handle_auth_request(sess_baton, pool));
1406 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word));
1407 SVN_ERR(interpret_kind(kind_word, pool, kind));
1408 return SVN_NO_ERROR;
1412 /* If ERR is a command not supported error, wrap it in a
1413 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1414 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1415 const char *msg)
1417 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1418 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1419 msg);
1420 return err;
1424 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1425 const char *path, svn_revnum_t rev,
1426 svn_dirent_t **dirent, apr_pool_t *pool)
1428 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1429 svn_ra_svn_conn_t *conn = sess_baton->conn;
1430 apr_array_header_t *list = NULL;
1431 const char *kind, *cdate, *cauthor;
1432 svn_revnum_t crev;
1433 svn_boolean_t has_props;
1434 apr_uint64_t size;
1435 svn_dirent_t *the_dirent;
1437 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "stat", "c(?r)", path, rev));
1439 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1440 _("'stat' not implemented")));
1442 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list));
1444 if (! list)
1446 *dirent = NULL;
1448 else
1450 SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "wnbr(?c)(?c)",
1451 &kind, &size, &has_props,
1452 &crev, &cdate, &cauthor));
1454 the_dirent = apr_palloc(pool, sizeof(*the_dirent));
1455 SVN_ERR(interpret_kind(kind, pool, &the_dirent->kind));
1456 the_dirent->size = size;/* FIXME: svn_filesize_t */
1457 the_dirent->has_props = has_props;
1458 the_dirent->created_rev = crev;
1459 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1460 the_dirent->last_author = cauthor;
1462 *dirent = the_dirent;
1465 return SVN_NO_ERROR;
1469 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1470 apr_hash_t **locations,
1471 const char *path,
1472 svn_revnum_t peg_revision,
1473 apr_array_header_t *location_revisions,
1474 apr_pool_t *pool)
1476 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1477 svn_ra_svn_conn_t *conn = sess_baton->conn;
1478 svn_revnum_t revision;
1479 svn_ra_svn_item_t *item;
1480 svn_boolean_t is_done;
1481 int i;
1482 const char *ret_path;
1484 /* Transmit the parameters. */
1485 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cr(!",
1486 "get-locations", path, peg_revision));
1487 for (i = 0; i < location_revisions->nelts; i++)
1489 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1490 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!r!", revision));
1493 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1495 /* Servers before 1.1 don't support this command. Check for this here. */
1496 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1497 _("'get-locations' not implemented")));
1499 /* Read the hash items. */
1500 is_done = FALSE;
1501 *locations = apr_hash_make(pool);
1502 while (!is_done)
1504 SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
1505 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1506 is_done = 1;
1507 else if (item->kind != SVN_RA_SVN_LIST)
1508 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1509 _("Location entry not a list"));
1510 else
1512 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "rc",
1513 &revision, &ret_path));
1514 ret_path = svn_path_canonicalize(ret_path, pool);
1515 apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1516 sizeof(revision)),
1517 sizeof(revision), ret_path);
1521 /* Read the response. This is so the server would have a chance to
1522 * report an error. */
1523 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1525 return SVN_NO_ERROR;
1528 static svn_error_t *
1529 ra_svn_get_location_segments(svn_ra_session_t *session,
1530 const char *path,
1531 svn_revnum_t peg_revision,
1532 svn_revnum_t start_rev,
1533 svn_revnum_t end_rev,
1534 svn_location_segment_receiver_t receiver,
1535 void *receiver_baton,
1536 apr_pool_t *pool)
1538 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1539 svn_ra_svn_conn_t *conn = sess_baton->conn;
1540 svn_ra_svn_item_t *item;
1541 svn_boolean_t is_done;
1542 svn_revnum_t range_start, range_end;
1543 const char *ret_path;
1544 svn_location_segment_t *segment;
1545 apr_pool_t *subpool = svn_pool_create(pool);
1547 /* Transmit the parameters. */
1548 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
1549 "get-location-segments",
1550 path, peg_revision, start_rev, end_rev));
1552 /* Servers before 1.1 don't support this command. Check for this here. */
1553 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1554 _("'get-location-segments' not implemented")));
1556 /* Parse the response. */
1557 is_done = FALSE;
1558 while (!is_done)
1560 svn_pool_clear(subpool);
1561 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
1562 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1563 is_done = 1;
1564 else if (item->kind != SVN_RA_SVN_LIST)
1565 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1566 _("Location segment entry not a list"));
1567 else
1569 segment = apr_pcalloc(subpool, sizeof(*segment));
1570 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "rr(?c)",
1571 &range_start, &range_end, &ret_path));
1572 if (! (SVN_IS_VALID_REVNUM(range_start)
1573 && SVN_IS_VALID_REVNUM(range_end)))
1574 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1575 _("Expected valid revision range"));
1576 if (ret_path)
1577 ret_path = svn_path_canonicalize(ret_path, subpool);
1578 segment->path = ret_path;
1579 segment->range_start = range_start;
1580 segment->range_end = range_end;
1581 SVN_ERR(receiver(segment, receiver_baton, subpool));
1584 svn_pool_destroy(subpool);
1586 /* Read the response. This is so the server would have a chance to
1587 * report an error. */
1588 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1590 return SVN_NO_ERROR;
1593 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
1594 const char *path,
1595 svn_revnum_t start, svn_revnum_t end,
1596 svn_boolean_t include_merged_revisions,
1597 svn_file_rev_handler_t handler,
1598 void *handler_baton, apr_pool_t *pool)
1600 svn_ra_svn__session_baton_t *sess_baton = session->priv;
1601 apr_pool_t *rev_pool, *chunk_pool;
1602 svn_ra_svn_item_t *item;
1603 const char *p;
1604 svn_revnum_t rev;
1605 apr_array_header_t *rev_proplist, *proplist;
1606 apr_hash_t *rev_props;
1607 apr_array_header_t *props;
1608 svn_boolean_t has_txdelta;
1609 svn_boolean_t had_revision = FALSE;
1610 svn_stream_t *stream;
1611 svn_txdelta_window_handler_t d_handler;
1612 void *d_baton;
1613 apr_size_t size;
1614 apr_uint64_t merged_rev_param;
1615 svn_boolean_t merged_rev;
1617 /* One sub-pool for each revision and one for each txdelta chunk.
1618 Note that the rev_pool must live during the following txdelta. */
1619 rev_pool = svn_pool_create(pool);
1620 chunk_pool = svn_pool_create(pool);
1622 SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs",
1623 "c(?r)(?r)b", path, start, end,
1624 include_merged_revisions));
1626 /* Servers before 1.1 don't support this command. Check for this here. */
1627 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1628 _("'get-file-revs' not implemented")));
1630 while (1)
1632 svn_pool_clear(rev_pool);
1633 svn_pool_clear(chunk_pool);
1634 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, rev_pool, &item));
1635 if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1636 break;
1637 /* Either we've got a correct revision or we will error out below. */
1638 had_revision = TRUE;
1639 if (item->kind != SVN_RA_SVN_LIST)
1640 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1641 _("Revision entry not a list"));
1643 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, rev_pool,
1644 "crll?B", &p, &rev, &rev_proplist,
1645 &proplist, &merged_rev_param));
1646 p = svn_path_canonicalize(p, rev_pool);
1647 SVN_ERR(svn_ra_svn_parse_proplist(rev_proplist, rev_pool, &rev_props));
1648 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
1649 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1650 merged_rev = FALSE;
1651 else
1652 merged_rev = (svn_boolean_t) merged_rev_param;
1654 /* Get the first delta chunk so we know if there is a delta. */
1655 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item));
1656 if (item->kind != SVN_RA_SVN_STRING)
1657 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1658 _("Text delta chunk not a string"));
1659 has_txdelta = item->u.string->len > 0;
1661 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
1662 has_txdelta ? &d_handler : NULL, &d_baton,
1663 props, rev_pool));
1665 /* Process the text delta if any. */
1666 if (has_txdelta)
1668 if (d_handler)
1669 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
1670 rev_pool);
1671 else
1672 stream = NULL;
1673 while (item->u.string->len > 0)
1675 size = item->u.string->len;
1676 if (stream)
1677 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
1678 svn_pool_clear(chunk_pool);
1680 SVN_ERR(svn_ra_svn_read_item(sess_baton->conn, chunk_pool, &item));
1681 if (item->kind != SVN_RA_SVN_STRING)
1682 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1683 _("Text delta chunk not a string"));
1685 if (stream)
1686 SVN_ERR(svn_stream_close(stream));
1690 SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton->conn, pool, ""));
1692 /* Return error if we didn't get any revisions. */
1693 if (!had_revision)
1694 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1695 _("The get-file-revs command didn't return "
1696 "any revisions"));
1698 svn_pool_destroy(chunk_pool);
1699 svn_pool_destroy(rev_pool);
1701 return SVN_NO_ERROR;
1704 /* For each path in PATH_REVS, send a 'lock' command to the server.
1705 Used with 1.2.x series servers which support locking, but of only
1706 one path at a time. ra_svn_lock(), which supports 'lock-many'
1707 is now the default. See svn_ra_lock() docstring for interface details. */
1708 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
1709 apr_hash_t *path_revs,
1710 const char *comment,
1711 svn_boolean_t steal_lock,
1712 svn_ra_lock_callback_t lock_func,
1713 void *lock_baton,
1714 apr_pool_t *pool)
1716 svn_ra_svn__session_baton_t *sess = session->priv;
1717 svn_ra_svn_conn_t* conn = sess->conn;
1718 apr_array_header_t *list;
1719 apr_hash_index_t *hi;
1720 apr_pool_t *iterpool = svn_pool_create(pool);
1722 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1724 svn_lock_t *lock;
1725 const void *key;
1726 const char *path;
1727 void *val;
1728 svn_revnum_t *revnum;
1729 svn_error_t *err, *callback_err = NULL;
1731 svn_pool_clear(iterpool);
1733 apr_hash_this(hi, &key, NULL, &val);
1734 path = key;
1735 revnum = val;
1737 SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "lock", "c(?c)b(?r)",
1738 path, comment,
1739 steal_lock, *revnum));
1741 /* Servers before 1.2 doesn't support locking. Check this here. */
1742 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
1743 _("Server doesn't support "
1744 "the lock command")));
1746 err = svn_ra_svn_read_cmd_response(conn, iterpool, "l", &list);
1748 if (!err)
1749 SVN_ERR(parse_lock(list, iterpool, &lock));
1751 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1752 return err;
1754 if (lock_func)
1755 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1756 err, iterpool);
1758 svn_error_clear(err);
1760 if (callback_err)
1761 return callback_err;
1764 svn_pool_destroy(iterpool);
1766 return SVN_NO_ERROR;
1769 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
1770 Used with 1.2.x series servers which support unlocking, but of only
1771 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
1772 now the default. See svn_ra_unlock() docstring for interface details. */
1773 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
1774 apr_hash_t *path_tokens,
1775 svn_boolean_t break_lock,
1776 svn_ra_lock_callback_t lock_func,
1777 void *lock_baton,
1778 apr_pool_t *pool)
1780 svn_ra_svn__session_baton_t *sess = session->priv;
1781 svn_ra_svn_conn_t* conn = sess->conn;
1782 apr_hash_index_t *hi;
1783 apr_pool_t *iterpool = svn_pool_create(pool);
1785 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1787 const void *key;
1788 const char *path;
1789 void *val;
1790 const char *token;
1791 svn_error_t *err, *callback_err = NULL;
1793 svn_pool_clear(iterpool);
1795 apr_hash_this(hi, &key, NULL, &val);
1796 path = key;
1797 if (strcmp(val, "") != 0)
1798 token = val;
1799 else
1800 token = NULL;
1802 SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "unlock", "c(?c)b",
1803 path, token, break_lock));
1805 /* Servers before 1.2 don't support locking. Check this here. */
1806 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
1807 _("Server doesn't support the unlock "
1808 "command")));
1810 err = svn_ra_svn_read_cmd_response(conn, iterpool, "");
1812 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1813 return err;
1815 if (lock_func)
1816 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
1818 svn_error_clear(err);
1820 if (callback_err)
1821 return callback_err;
1824 svn_pool_destroy(iterpool);
1826 return SVN_NO_ERROR;
1829 /* Tell the server to lock all paths in PATH_REVS.
1830 See svn_ra_lock() for interface details. */
1831 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
1832 apr_hash_t *path_revs,
1833 const char *comment,
1834 svn_boolean_t steal_lock,
1835 svn_ra_lock_callback_t lock_func,
1836 void *lock_baton,
1837 apr_pool_t *pool)
1839 svn_ra_svn__session_baton_t *sess = session->priv;
1840 svn_ra_svn_conn_t *conn = sess->conn;
1841 apr_hash_index_t *hi;
1842 svn_ra_svn_item_t *elt;
1843 svn_error_t *err, *callback_err = SVN_NO_ERROR;
1844 apr_pool_t *subpool = svn_pool_create(pool);
1845 const char *status;
1846 svn_lock_t *lock;
1847 apr_array_header_t *list = NULL;
1849 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)b(!", "lock-many",
1850 comment, steal_lock));
1852 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1854 const void *key;
1855 const char *path;
1856 void *val;
1857 svn_revnum_t *revnum;
1859 svn_pool_clear(subpool);
1860 apr_hash_this(hi, &key, NULL, &val);
1861 path = key;
1862 revnum = val;
1864 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?r)", path, *revnum));
1867 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1869 err = handle_auth_request(sess, pool);
1871 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
1872 * to 'lock'. */
1873 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1875 svn_error_clear(err);
1876 return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
1877 lock_func, lock_baton, pool);
1880 if (err)
1881 return err;
1883 /* Loop over responses to get lock information. */
1884 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1886 const void *key;
1887 const char *path;
1889 apr_hash_this(hi, &key, NULL, NULL);
1890 path = key;
1892 svn_pool_clear(subpool);
1893 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &elt));
1895 /* The server might have encountered some sort of fatal error in
1896 the middle of the request list. If this happens, it will
1897 transmit "done" to end the lock-info early, and then the
1898 overall command response will talk about the fatal error. */
1899 if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
1900 break;
1902 if (elt->kind != SVN_RA_SVN_LIST)
1903 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1904 _("Lock response not a list"));
1906 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "wl", &status,
1907 &list));
1909 if (strcmp(status, "failure") == 0)
1910 err = svn_ra_svn__handle_failure_status(list, subpool);
1911 else if (strcmp(status, "success") == 0)
1913 SVN_ERR(parse_lock(list, subpool, &lock));
1914 err = NULL;
1916 else
1917 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1918 _("Unknown status for lock command"));
1920 if (lock_func)
1921 callback_err = lock_func(lock_baton, path, TRUE,
1922 err ? NULL : lock,
1923 err, subpool);
1924 else
1925 callback_err = SVN_NO_ERROR;
1927 svn_error_clear(err);
1929 if (callback_err)
1930 return callback_err;
1933 /* If we didn't break early above, and the whole hash was traversed,
1934 read the final "done" from the server. */
1935 if (!hi)
1937 SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt));
1938 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
1939 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1940 _("Didn't receive end marker for lock "
1941 "responses"));
1944 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
1946 svn_pool_destroy(subpool);
1948 return SVN_NO_ERROR;
1951 /* Tell the server to unlock all paths in PATH_TOKENS.
1952 See svn_ra_unlock() for interface details. */
1953 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
1954 apr_hash_t *path_tokens,
1955 svn_boolean_t break_lock,
1956 svn_ra_lock_callback_t lock_func,
1957 void *lock_baton,
1958 apr_pool_t *pool)
1960 svn_ra_svn__session_baton_t *sess = session->priv;
1961 svn_ra_svn_conn_t *conn = sess->conn;
1962 apr_hash_index_t *hi;
1963 apr_pool_t *subpool = svn_pool_create(pool);
1964 svn_error_t *err, *callback_err = NULL;
1965 svn_ra_svn_item_t *elt;
1966 const char *status = NULL;
1967 apr_array_header_t *list = NULL;
1968 const void *key;
1969 const char *path;
1971 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(b(!", "unlock-many",
1972 break_lock));
1974 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1976 void *val;
1977 const char *token;
1979 svn_pool_clear(subpool);
1980 apr_hash_this(hi, &key, NULL, &val);
1981 path = key;
1983 if (strcmp(val, "") != 0)
1984 token = val;
1985 else
1986 token = NULL;
1988 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?c)", path, token));
1991 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1993 err = handle_auth_request(sess, pool);
1995 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
1996 * to 'unlock'.
1998 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2000 svn_error_clear(err);
2001 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2002 lock_baton, pool);
2005 if (err)
2006 return err;
2008 /* Loop over responses to unlock files. */
2009 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2011 svn_pool_clear(subpool);
2013 SVN_ERR(svn_ra_svn_read_item(conn, subpool, &elt));
2015 /* The server might have encountered some sort of fatal error in
2016 the middle of the request list. If this happens, it will
2017 transmit "done" to end the lock-info early, and then the
2018 overall command response will talk about the fatal error. */
2019 if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2020 break;
2022 apr_hash_this(hi, &key, NULL, NULL);
2023 path = key;
2025 if (elt->kind != SVN_RA_SVN_LIST)
2026 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2027 _("Unlock response not a list"));
2029 SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "wl", &status,
2030 &list));
2032 if (strcmp(status, "failure") == 0)
2033 err = svn_ra_svn__handle_failure_status(list, subpool);
2034 else if (strcmp(status, "success") == 0)
2036 SVN_ERR(svn_ra_svn_parse_tuple(list, subpool, "c", &path));
2037 err = SVN_NO_ERROR;
2039 else
2040 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2041 _("Unknown status for unlock command"));
2043 if (lock_func)
2044 callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2045 subpool);
2046 else
2047 callback_err = SVN_NO_ERROR;
2049 svn_error_clear(err);
2051 if (callback_err)
2052 return callback_err;
2055 /* If we didn't break early above, and the whole hash was traversed,
2056 read the final "done" from the server. */
2057 if (!hi)
2059 SVN_ERR(svn_ra_svn_read_item(conn, pool, &elt));
2060 if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2061 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2062 _("Didn't receive end marker for unlock "
2063 "responses"));
2066 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
2068 svn_pool_destroy(subpool);
2070 return SVN_NO_ERROR;
2073 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2074 svn_lock_t **lock,
2075 const char *path,
2076 apr_pool_t *pool)
2078 svn_ra_svn__session_baton_t *sess = session->priv;
2079 svn_ra_svn_conn_t* conn = sess->conn;
2080 apr_array_header_t *list;
2082 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-lock", "c", path));
2084 /* Servers before 1.2 doesn't support locking. Check this here. */
2085 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2086 _("Server doesn't support the get-lock "
2087 "command")));
2089 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list));
2090 if (list)
2091 SVN_ERR(parse_lock(list, pool, lock));
2092 else
2093 *lock = NULL;
2095 return SVN_NO_ERROR;
2098 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2099 apr_hash_t **locks,
2100 const char *path,
2101 apr_pool_t *pool)
2103 svn_ra_svn__session_baton_t *sess = session->priv;
2104 svn_ra_svn_conn_t* conn = sess->conn;
2105 apr_array_header_t *list;
2106 int i;
2107 svn_ra_svn_item_t *elt;
2108 svn_lock_t *lock;
2110 SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-locks", "c", path));
2112 /* Servers before 1.2 doesn't support locking. Check this here. */
2113 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2114 _("Server doesn't support the get-lock "
2115 "command")));
2117 SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &list));
2119 *locks = apr_hash_make(pool);
2121 for (i = 0; i < list->nelts; ++i)
2123 elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2125 if (elt->kind != SVN_RA_SVN_LIST)
2126 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2127 _("Lock element not a list"));
2128 SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2129 apr_hash_set(*locks, lock->path, APR_HASH_KEY_STRING, lock);
2132 return SVN_NO_ERROR;
2136 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2137 svn_revnum_t revision,
2138 svn_revnum_t low_water_mark,
2139 svn_boolean_t send_deltas,
2140 const svn_delta_editor_t *editor,
2141 void *edit_baton,
2142 apr_pool_t *pool)
2144 svn_ra_svn__session_baton_t *sess = session->priv;
2146 SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay", "rrb", revision,
2147 low_water_mark, send_deltas));
2149 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2150 _("Server doesn't support the replay "
2151 "command")));
2153 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2154 NULL, TRUE));
2156 SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
2158 return SVN_NO_ERROR;
2162 static svn_error_t *
2163 ra_svn_replay_range(svn_ra_session_t *session,
2164 svn_revnum_t start_revision,
2165 svn_revnum_t end_revision,
2166 svn_revnum_t low_water_mark,
2167 svn_boolean_t send_deltas,
2168 svn_ra_replay_revstart_callback_t revstart_func,
2169 svn_ra_replay_revfinish_callback_t revfinish_func,
2170 void *replay_baton,
2171 apr_pool_t *pool)
2173 svn_ra_svn__session_baton_t *sess = session->priv;
2174 apr_pool_t *iterpool;
2175 svn_revnum_t rev;
2177 SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay-range", "rrrb",
2178 start_revision, end_revision,
2179 low_water_mark, send_deltas));
2181 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2182 _("Server doesn't support the replay-range "
2183 "command")));
2185 iterpool = svn_pool_create(pool);
2186 for (rev = start_revision; rev <= end_revision; rev++)
2188 const svn_delta_editor_t *editor;
2189 void *edit_baton;
2190 apr_hash_t *rev_props;
2191 const char *word;
2192 apr_array_header_t *list;
2194 svn_pool_clear(iterpool);
2196 SVN_ERR(svn_ra_svn_read_tuple(sess->conn, iterpool,
2197 "wl", &word, &list));
2198 if (strcmp(word, "revprops") != 0)
2199 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2200 _("Expected 'revprops', found '%s'"),
2201 word);
2203 SVN_ERR(svn_ra_svn_parse_proplist(list, iterpool, &rev_props));
2205 SVN_ERR(revstart_func(rev, replay_baton,
2206 &editor, &edit_baton,
2207 rev_props,
2208 iterpool));
2209 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2210 editor, edit_baton,
2211 NULL, TRUE));
2212 SVN_ERR(revfinish_func(rev, replay_baton,
2213 editor, edit_baton,
2214 rev_props,
2215 iterpool));
2217 svn_pool_destroy(iterpool);
2219 SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
2221 return SVN_NO_ERROR;
2225 static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
2226 svn_boolean_t *has,
2227 const char *capability,
2228 apr_pool_t *pool)
2230 svn_ra_svn__session_baton_t *sess = session->priv;
2232 *has = FALSE;
2234 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0)
2235 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_DEPTH);
2236 else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
2237 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_MERGEINFO);
2238 else if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0)
2239 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_LOG_REVPROPS);
2240 else if (strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0)
2241 *has = svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_PARTIAL_REPLAY);
2242 else /* Don't know any other capabilities, so error. */
2244 return svn_error_createf
2245 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2246 _("Don't know anything about capability '%s'"), capability);
2249 return SVN_NO_ERROR;
2253 static const svn_ra__vtable_t ra_svn_vtable = {
2254 svn_ra_svn_version,
2255 ra_svn_get_description,
2256 ra_svn_get_schemes,
2257 ra_svn_open,
2258 ra_svn_reparent,
2259 ra_svn_get_session_url,
2260 ra_svn_get_latest_rev,
2261 ra_svn_get_dated_rev,
2262 ra_svn_change_rev_prop,
2263 ra_svn_rev_proplist,
2264 ra_svn_rev_prop,
2265 ra_svn_commit,
2266 ra_svn_get_file,
2267 ra_svn_get_dir,
2268 ra_svn_get_mergeinfo,
2269 ra_svn_update,
2270 ra_svn_switch,
2271 ra_svn_status,
2272 ra_svn_diff,
2273 ra_svn_log,
2274 ra_svn_check_path,
2275 ra_svn_stat,
2276 ra_svn_get_uuid,
2277 ra_svn_get_repos_root,
2278 ra_svn_get_locations,
2279 ra_svn_get_location_segments,
2280 ra_svn_get_file_revs,
2281 ra_svn_lock,
2282 ra_svn_unlock,
2283 ra_svn_get_lock,
2284 ra_svn_get_locks,
2285 ra_svn_replay,
2286 ra_svn_has_capability,
2287 ra_svn_replay_range,
2290 svn_error_t *
2291 svn_ra_svn__init(const svn_version_t *loader_version,
2292 const svn_ra__vtable_t **vtable,
2293 apr_pool_t *pool)
2295 static const svn_version_checklist_t checklist[] =
2297 { "svn_subr", svn_subr_version },
2298 { "svn_delta", svn_delta_version },
2299 { NULL, NULL }
2302 SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist));
2304 /* Simplified version check to make sure we can safely use the
2305 VTABLE parameter. The RA loader does a more exhaustive check. */
2306 if (loader_version->major != SVN_VER_MAJOR)
2308 return svn_error_createf
2309 (SVN_ERR_VERSION_MISMATCH, NULL,
2310 _("Unsupported RA loader version (%d) for ra_svn"),
2311 loader_version->major);
2314 *vtable = &ra_svn_vtable;
2316 #ifdef SVN_HAVE_SASL
2317 SVN_ERR(svn_ra_svn__sasl_init());
2318 #endif
2320 return SVN_NO_ERROR;
2323 /* Compatibility wrapper for the 1.1 and before API. */
2324 #define NAME "ra_svn"
2325 #define DESCRIPTION RA_SVN_DESCRIPTION
2326 #define VTBL ra_svn_vtable
2327 #define INITFUNC svn_ra_svn__init
2328 #define COMPAT_INITFUNC svn_ra_svn_init
2329 #include "../libsvn_ra/wrapper_template.h"