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
25 #include <apr_general.h>
26 #include <apr_strings.h>
27 #include <apr_network_io.h>
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_error.h"
37 #include "svn_pools.h"
38 #include "svn_config.h"
39 #include "svn_private_config.h"
41 #include "../libsvn_ra/ra_loader.h"
42 #include "svn_ra_svn.h"
44 #include "svn_props.h"
45 #include "svn_mergeinfo.h"
50 #define DO_AUTH svn_ra_svn__do_cyrus_auth
52 #define DO_AUTH svn_ra_svn__do_internal_auth
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)
65 svn_ra_svn__session_baton_t
*sess_baton
;
67 svn_revnum_t
*new_rev
;
68 svn_commit_callback2_t callback
;
70 } ra_svn_commit_callback_baton_t
;
73 svn_ra_svn__session_baton_t
*sess_baton
;
74 svn_ra_svn_conn_t
*conn
;
76 const svn_delta_editor_t
*editor
;
78 } ra_svn_reporter_baton_t
;
80 /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
82 static void parse_tunnel(const char *url
, const char **tunnel
,
89 if (strncasecmp(url
, "svn", 3) != 0)
93 /* Get the tunnel specification, if any. */
100 *tunnel
= apr_pstrmemdup(pool
, url
, p
- url
);
105 static svn_error_t
*make_connection(const char *hostname
, unsigned short port
,
106 apr_socket_t
**sock
, apr_pool_t
*pool
)
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. */
117 #ifdef MAX_SECS_TO_LINGER
118 status
= apr_socket_create(sock
, APR_INET6
, SOCK_STREAM
, pool
);
120 status
= apr_socket_create(sock
, APR_INET6
, SOCK_STREAM
,
121 APR_PROTO_TCP
, pool
);
125 apr_socket_close(*sock
);
130 /* Resolve the hostname. */
131 status
= apr_sockaddr_info_get(&sa
, hostname
, family
, port
, 0, pool
);
133 return svn_error_createf(status
, NULL
, _("Unknown hostname '%s'"),
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
);
141 status
= apr_socket_create(sock
, sa
->family
, SOCK_STREAM
, APR_PROTO_TCP
,
145 return svn_error_wrap_apr(status
, _("Can't create socket"));
147 status
= apr_socket_connect(*sock
, sa
);
149 return svn_error_wrap_apr(status
, _("Can't connect to host '%s'"),
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
,
159 apr_array_header_t
**diffs
)
161 svn_ra_svn_item_t
*elt
;
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
,
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
,
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
));
193 SVN_ERR(svn_time_from_cstring(&(*lock
)->expiration_date
, edate
, pool
));
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
;
209 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
210 _("Unrecognized node kind '%s' from server"),
215 /* --- AUTHENTICATION ROUTINES --- */
217 svn_error_t
*svn_ra_svn__auth_response(svn_ra_svn_conn_t
*conn
,
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
,
227 svn_ra_svn_conn_t
*conn
= sess
->conn
;
228 apr_array_header_t
*mechlist
;
231 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "lc", &mechlist
, &realm
));
232 if (mechlist
->nelts
== 0)
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
,
242 svn_boolean_t start_empty
,
243 const char *lock_token
,
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
)));
254 static svn_error_t
*ra_svn_delete_path(void *baton
, const char *path
,
257 ra_svn_reporter_baton_t
*b
= baton
;
259 SVN_ERR(svn_ra_svn_write_cmd(b
->conn
, pool
, "delete-path", "c", path
));
263 static svn_error_t
*ra_svn_link_path(void *baton
, const char *path
,
267 svn_boolean_t start_empty
,
268 const char *lock_token
,
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
)));
279 static svn_error_t
*ra_svn_finish_report(void *baton
,
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
,
288 SVN_ERR(svn_ra_svn_read_cmd_response(b
->conn
, b
->pool
, ""));
292 static svn_error_t
*ra_svn_abort_report(void *baton
,
295 ra_svn_reporter_baton_t
*b
= baton
;
297 SVN_ERR(svn_ra_svn_write_cmd(b
->conn
, b
->pool
, "abort-report", ""));
301 static svn_ra_reporter3_t ra_svn_reporter
= {
305 ra_svn_finish_report
,
309 static void ra_svn_get_reporter(svn_ra_svn__session_baton_t
*sess_baton
,
311 const svn_delta_editor_t
*editor
,
315 const svn_ra_reporter3_t
**reporter
,
318 ra_svn_reporter_baton_t
*b
;
319 const svn_delta_editor_t
*filter_editor
;
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
,
330 editor
, edit_baton
, depth
,
331 *target
? TRUE
: FALSE
,
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
;
342 b
->edit_baton
= edit_baton
;
344 *reporter
= &ra_svn_reporter
;
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
,
354 apr_hash_t
*config
, apr_pool_t
*pool
)
357 const char *val
, *var
, *cmd
;
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";
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. */
381 len
= strcspn(val
, " ");
382 var
= apr_pstrmemdup(pool
, val
, len
);
390 return svn_error_createf(SVN_ERR_BAD_URL
, NULL
,
391 _("Tunnel scheme %s requires environment "
392 "variable %s to be defined"), tunnel
,
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";
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
,
424 svn_ra_svn_conn_t
*conn
;
425 apr_file_t
*in_file
, *out_file
;
428 if (apr_file_open_stdin(&in_file
, pool
)
429 || apr_file_open_stdout(&out_file
, pool
))
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
,
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
));
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
,
489 apr_status_t apr_err
;
491 apr_err
= apr_uri_parse(pool
, url
, uri
);
494 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
495 _("Illegal svn repository URL '%s'"), url
);
498 uri
->port
= SVN_RA_SVN_PORT
;
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
,
510 const apr_uri_t
*uri
,
511 const char **tunnel_argv
,
512 const svn_ra_callbacks2_t
*callbacks
,
513 void *callbacks_baton
,
516 svn_ra_svn__session_baton_t
*sess
;
517 svn_ra_svn_conn_t
*conn
;
519 apr_uint64_t minver
, maxver
;
520 apr_array_header_t
*mechlist
, *server_caplist
, *repos_caplist
;
522 sess
= apr_palloc(pool
, sizeof(*sess
));
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
,
530 sess
->tunnel_argv
= tunnel_argv
;
531 sess
->callbacks
= callbacks
;
532 sess
->callbacks_baton
= callbacks_baton
;
533 sess
->bytes_read
= sess
->bytes_written
= 0;
536 SVN_ERR(make_tunnel(tunnel_argv
, &conn
, pool
));
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. */
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. */
554 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION
, NULL
,
555 _("Server requires minimum version %d"),
558 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION
, NULL
,
559 _("Server only supports versions up to %d"),
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
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
,
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
));
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 "
611 #define RA_SVN_DESCRIPTION \
612 N_("Module for accessing a repository using the svn network protocol.\n" \
613 " - with Cyrus SASL authentication")
615 #define RA_SVN_DESCRIPTION \
616 N_("Module for accessing a repository using the svn network protocol.")
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
};
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
,
640 apr_pool_t
*sess_pool
= svn_pool_create(pool
);
641 svn_ra_svn__session_baton_t
*sess
;
642 const char *tunnel
, **tunnel_argv
;
645 SVN_ERR(parse_url(url
, &uri
, sess_pool
));
647 parse_tunnel(url
, &tunnel
, pool
);
650 SVN_ERR(find_tunnel_agent(tunnel
, uri
.hostinfo
, &tunnel_argv
, config
,
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
;
664 static svn_error_t
*ra_svn_reparent(svn_ra_session_t
*ra_session
,
668 svn_ra_svn__session_baton_t
*sess
= ra_session
->priv
;
669 svn_ra_svn_conn_t
*conn
= sess
->conn
;
671 apr_pool_t
*sess_pool
;
672 svn_ra_svn__session_baton_t
*new_sess
;
675 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "reparent", "c", url
));
676 err
= handle_auth_request(sess
, pool
);
679 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
680 sess
->url
= apr_pstrdup(sess
->pool
, url
);
683 else if (err
->apr_err
!= SVN_ERR_RA_SVN_UNKNOWN_CMD
)
686 /* Servers before 1.4 doesn't support this command; try to reconnect
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
);
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. */
699 svn_pool_destroy(sess_pool
);
703 /* We have a new connection, assign it and destroy the old. */
704 ra_session
->priv
= new_sess
;
705 svn_pool_destroy(sess
->pool
);
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
);
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
));
730 static svn_error_t
*ra_svn_get_dated_rev(svn_ra_session_t
*session
,
731 svn_revnum_t
*rev
, apr_time_t tm
,
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
));
744 static svn_error_t
*ra_svn_change_rev_prop(svn_ra_session_t
*session
, svn_revnum_t rev
,
746 const svn_string_t
*value
,
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",
754 SVN_ERR(handle_auth_request(sess_baton
, pool
));
755 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
759 static svn_error_t
*ra_svn_get_uuid(svn_ra_session_t
*session
, const char **uuid
,
762 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
763 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
769 static svn_error_t
*ra_svn_get_repos_root(svn_ra_session_t
*session
, const char **url
,
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
;
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
));
796 static svn_error_t
*ra_svn_rev_prop(svn_ra_session_t
*session
, svn_revnum_t rev
,
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
));
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
,
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
,
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
,
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
856 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c(!", "commit",
860 iterpool
= svn_pool_create(pool
);
861 for (hi
= apr_hash_first(pool
, lock_tokens
); hi
; hi
= apr_hash_next(hi
))
865 const char *path
, *token
;
866 svn_pool_clear(iterpool
);
867 apr_hash_this(hi
, &key
, NULL
, &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
;
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
);
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
,
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",
919 SVN_ERR(svn_ra_svn_parse_proplist(proplist
, pool
, props
));
921 /* We're done if the contents weren't wanted. */
925 if (expected_checksum
)
926 apr_md5_init(&md5_context
);
928 /* Read the file's contents. */
929 iterpool
= svn_pool_create(pool
);
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)
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
);
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
,
975 apr_uint32_t dirent_fields
,
978 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
979 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
981 apr_array_header_t
*proplist
, *dirlist
;
983 svn_ra_svn_item_t
*elt
;
984 const char *name
, *kind
, *cdate
, *cauthor
;
985 svn_boolean_t has_props
;
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
,
1026 SVN_ERR(svn_ra_svn_parse_proplist(proplist
, pool
, props
));
1028 /* We're done if dirents aren't wanted. */
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
,
1067 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1068 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
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
));
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",
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
,
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
,
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
,
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
;
1228 const char *path
, *cpath
, *action
, *copy_path
;
1229 svn_string_t
*author
, *date
, *message
;
1230 svn_ra_svn_item_t
*item
, *elt
;
1232 apr_array_header_t
*cplist
, *rplist
;
1234 svn_revnum_t rev
, copy_rev
;
1235 svn_log_changed_path_t
*change
;
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"));
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
));
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
, "!))"));
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
);
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)
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"
1304 if (has_children_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
1305 has_children
= FALSE
;
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
, ©_path
,
1328 cpath
= svn_path_canonicalize(cpath
, subpool
);
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
);
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
;
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. */
1357 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_AUTHOR
,
1358 APR_HASH_KEY_STRING
, author
);
1360 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_DATE
,
1361 APR_HASH_KEY_STRING
, date
);
1363 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_LOG
,
1364 APR_HASH_KEY_STRING
, message
);
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
,
1417 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1418 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, 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
;
1433 svn_boolean_t has_props
;
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
));
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
,
1472 svn_revnum_t peg_revision
,
1473 apr_array_header_t
*location_revisions
,
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
;
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. */
1501 *locations
= apr_hash_make(pool
);
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)
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"));
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
,
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
,
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
,
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. */
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)
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"));
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"));
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
,
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
;
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
;
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")));
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)
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
)
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
,
1665 /* Process the text delta if any. */
1669 stream
= svn_txdelta_parse_svndiff(d_handler
, d_baton
, TRUE
,
1673 while (item
->u
.string
->len
> 0)
1675 size
= item
->u
.string
->len
;
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"));
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. */
1694 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1695 _("The get-file-revs command didn't return "
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
,
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
))
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
);
1737 SVN_ERR(svn_ra_svn_write_cmd(conn
, iterpool
, "lock", "c(?c)b(?r)",
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
);
1749 SVN_ERR(parse_lock(list
, iterpool
, &lock
));
1751 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
1755 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
1758 svn_error_clear(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
,
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
))
1791 svn_error_t
*err
, *callback_err
= NULL
;
1793 svn_pool_clear(iterpool
);
1795 apr_hash_this(hi
, &key
, NULL
, &val
);
1797 if (strcmp(val
, "") != 0)
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 "
1810 err
= svn_ra_svn_read_cmd_response(conn
, iterpool
, "");
1812 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
1816 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, pool
);
1818 svn_error_clear(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
,
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
);
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
))
1857 svn_revnum_t
*revnum
;
1859 svn_pool_clear(subpool
);
1860 apr_hash_this(hi
, &key
, NULL
, &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
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
);
1883 /* Loop over responses to get lock information. */
1884 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1889 apr_hash_this(hi
, &key
, NULL
, NULL
);
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)
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
,
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
));
1917 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1918 _("Unknown status for lock command"));
1921 callback_err
= lock_func(lock_baton
, path
, TRUE
,
1925 callback_err
= SVN_NO_ERROR
;
1927 svn_error_clear(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. */
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 "
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
,
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
;
1971 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(b(!", "unlock-many",
1974 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1979 svn_pool_clear(subpool
);
1980 apr_hash_this(hi
, &key
, NULL
, &val
);
1983 if (strcmp(val
, "") != 0)
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
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
,
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))
2022 apr_hash_this(hi
, &key
, NULL
, NULL
);
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
,
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
));
2040 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2041 _("Unknown status for unlock command"));
2044 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
,
2047 callback_err
= SVN_NO_ERROR
;
2049 svn_error_clear(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. */
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 "
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
,
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 "
2089 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &list
));
2091 SVN_ERR(parse_lock(list
, pool
, lock
));
2095 return SVN_NO_ERROR
;
2098 static svn_error_t
*ra_svn_get_locks(svn_ra_session_t
*session
,
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
;
2107 svn_ra_svn_item_t
*elt
;
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 "
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
,
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 "
2153 SVN_ERR(svn_ra_svn_drive_editor2(sess
->conn
, pool
, editor
, edit_baton
,
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
,
2173 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2174 apr_pool_t
*iterpool
;
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 "
2185 iterpool
= svn_pool_create(pool
);
2186 for (rev
= start_revision
; rev
<= end_revision
; rev
++)
2188 const svn_delta_editor_t
*editor
;
2190 apr_hash_t
*rev_props
;
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'"),
2203 SVN_ERR(svn_ra_svn_parse_proplist(list
, iterpool
, &rev_props
));
2205 SVN_ERR(revstart_func(rev
, replay_baton
,
2206 &editor
, &edit_baton
,
2209 SVN_ERR(svn_ra_svn_drive_editor2(sess
->conn
, iterpool
,
2212 SVN_ERR(revfinish_func(rev
, replay_baton
,
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
,
2227 const char *capability
,
2230 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
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
= {
2255 ra_svn_get_description
,
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
,
2268 ra_svn_get_mergeinfo
,
2277 ra_svn_get_repos_root
,
2278 ra_svn_get_locations
,
2279 ra_svn_get_location_segments
,
2280 ra_svn_get_file_revs
,
2286 ra_svn_has_capability
,
2287 ra_svn_replay_range
,
2291 svn_ra_svn__init(const svn_version_t
*loader_version
,
2292 const svn_ra__vtable_t
**vtable
,
2295 static const svn_version_checklist_t checklist
[] =
2297 { "svn_subr", svn_subr_version
},
2298 { "svn_delta", svn_delta_version
},
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());
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"