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
, *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
, &caplist
));
551 /* We support protocol version 2. */
553 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION
, NULL
,
554 _("Server requires minimum version %d"),
557 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION
, NULL
,
558 _("Server only supports versions up to %d"),
560 SVN_ERR(svn_ra_svn_set_capabilities(conn
, caplist
));
562 /* All released versions of Subversion support edit-pipeline,
563 * so we do not support servers that do not. */
564 if (! svn_ra_svn_has_capability(conn
, SVN_RA_SVN_CAP_EDIT_PIPELINE
))
565 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION
, NULL
,
566 _("Server does not support edit pipelining"));
568 /* In protocol version 2, we send back our protocol version, our
569 * capability list, and the URL, and subsequently there is an auth
571 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "n(wwwwww)c", (apr_uint64_t
) 2,
572 SVN_RA_SVN_CAP_EDIT_PIPELINE
,
573 SVN_RA_SVN_CAP_SVNDIFF1
,
574 SVN_RA_SVN_CAP_ABSENT_ENTRIES
,
575 SVN_RA_SVN_CAP_DEPTH
,
576 SVN_RA_SVN_CAP_MERGEINFO
,
577 SVN_RA_SVN_CAP_LOG_REVPROPS
,
579 SVN_ERR(handle_auth_request(sess
, pool
));
581 /* This is where the security layer would go into effect if we
582 * supported security layers, which is a ways off. */
584 /* Read the repository's uuid and root URL. */
585 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "c?c", &conn
->uuid
,
587 if (conn
->repos_root
)
589 conn
->repos_root
= svn_path_canonicalize(conn
->repos_root
, pool
);
590 /* We should check that the returned string is a prefix of url, since
591 that's the API guarantee, but this isn't true for 1.0 servers.
592 Checking the length prevents client crashes. */
593 if (strlen(conn
->repos_root
) > strlen(url
))
594 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
595 _("Impossibly long repository root from "
605 #define RA_SVN_DESCRIPTION \
606 N_("Module for accessing a repository using the svn network protocol.")
608 static const char *ra_svn_get_description(void)
610 return _(RA_SVN_DESCRIPTION
);
613 static const char * const *
614 ra_svn_get_schemes(apr_pool_t
*pool
)
616 static const char *schemes
[] = { "svn", NULL
};
623 static svn_error_t
*ra_svn_open(svn_ra_session_t
*session
, const char *url
,
624 const svn_ra_callbacks2_t
*callbacks
,
625 void *callback_baton
,
629 apr_pool_t
*sess_pool
= svn_pool_create(pool
);
630 svn_ra_svn__session_baton_t
*sess
;
631 const char *tunnel
, **tunnel_argv
;
634 SVN_ERR(parse_url(url
, &uri
, sess_pool
));
636 parse_tunnel(url
, &tunnel
, pool
);
639 SVN_ERR(find_tunnel_agent(tunnel
, uri
.hostinfo
, &tunnel_argv
, config
,
644 /* We open the session in a subpool so we can get rid of it if we
645 reparent with a server that doesn't support reparenting. */
646 SVN_ERR(open_session(&sess
, url
, &uri
, tunnel_argv
,
647 callbacks
, callback_baton
, sess_pool
));
648 session
->priv
= sess
;
653 static svn_error_t
*ra_svn_reparent(svn_ra_session_t
*ra_session
,
657 svn_ra_svn__session_baton_t
*sess
= ra_session
->priv
;
658 svn_ra_svn_conn_t
*conn
= sess
->conn
;
660 apr_pool_t
*sess_pool
;
661 svn_ra_svn__session_baton_t
*new_sess
;
664 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "reparent", "c", url
));
665 err
= handle_auth_request(sess
, pool
);
668 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
669 sess
->url
= apr_pstrdup(sess
->pool
, url
);
672 else if (err
->apr_err
!= SVN_ERR_RA_SVN_UNKNOWN_CMD
)
675 /* Servers before 1.4 doesn't support this command; try to reconnect
677 svn_error_clear(err
);
678 /* Create a new subpool of the RA session pool. */
679 sess_pool
= svn_pool_create(ra_session
->pool
);
680 err
= parse_url(url
, &uri
, sess_pool
);
682 err
= open_session(&new_sess
, url
, &uri
, sess
->tunnel_argv
,
683 sess
->callbacks
, sess
->callbacks_baton
, sess_pool
);
684 /* We destroy the new session pool on error, since it is allocated in
685 the main session pool. */
688 svn_pool_destroy(sess_pool
);
692 /* We have a new connection, assign it and destroy the old. */
693 ra_session
->priv
= new_sess
;
694 svn_pool_destroy(sess
->pool
);
699 static svn_error_t
*ra_svn_get_session_url(svn_ra_session_t
*session
,
700 const char **url
, apr_pool_t
*pool
)
702 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
703 *url
= apr_pstrdup(pool
, sess
->url
);
707 static svn_error_t
*ra_svn_get_latest_rev(svn_ra_session_t
*session
,
708 svn_revnum_t
*rev
, apr_pool_t
*pool
)
710 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
711 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
713 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-latest-rev", ""));
714 SVN_ERR(handle_auth_request(sess_baton
, pool
));
715 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "r", rev
));
719 static svn_error_t
*ra_svn_get_dated_rev(svn_ra_session_t
*session
,
720 svn_revnum_t
*rev
, apr_time_t tm
,
723 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
724 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
726 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-dated-rev", "c",
727 svn_time_to_cstring(tm
, pool
)));
728 SVN_ERR(handle_auth_request(sess_baton
, pool
));
729 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "r", rev
));
733 static svn_error_t
*ra_svn_change_rev_prop(svn_ra_session_t
*session
, svn_revnum_t rev
,
735 const svn_string_t
*value
,
738 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
739 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
741 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "change-rev-prop", "rc?s",
743 SVN_ERR(handle_auth_request(sess_baton
, pool
));
744 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
748 static svn_error_t
*ra_svn_get_uuid(svn_ra_session_t
*session
, const char **uuid
,
751 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
752 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
758 static svn_error_t
*ra_svn_get_repos_root(svn_ra_session_t
*session
, const char **url
,
761 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
762 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
764 if (!conn
->repos_root
)
765 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION
, NULL
,
766 _("Server did not send repository root"));
767 *url
= conn
->repos_root
;
771 static svn_error_t
*ra_svn_rev_proplist(svn_ra_session_t
*session
, svn_revnum_t rev
,
772 apr_hash_t
**props
, apr_pool_t
*pool
)
774 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
775 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
776 apr_array_header_t
*proplist
;
778 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "rev-proplist", "r", rev
));
779 SVN_ERR(handle_auth_request(sess_baton
, pool
));
780 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "l", &proplist
));
781 SVN_ERR(svn_ra_svn_parse_proplist(proplist
, pool
, props
));
785 static svn_error_t
*ra_svn_rev_prop(svn_ra_session_t
*session
, svn_revnum_t rev
,
787 svn_string_t
**value
, apr_pool_t
*pool
)
789 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
790 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
792 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "rev-prop", "rc", rev
, name
));
793 SVN_ERR(handle_auth_request(sess_baton
, pool
));
794 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?s)", value
));
798 static svn_error_t
*ra_svn_end_commit(void *baton
)
800 ra_svn_commit_callback_baton_t
*ccb
= baton
;
801 svn_commit_info_t
*commit_info
= svn_create_commit_info(ccb
->pool
);
803 SVN_ERR(handle_auth_request(ccb
->sess_baton
, ccb
->pool
));
804 SVN_ERR(svn_ra_svn_read_tuple(ccb
->sess_baton
->conn
, ccb
->pool
,
806 &(commit_info
->revision
),
807 &(commit_info
->date
),
808 &(commit_info
->author
),
809 &(commit_info
->post_commit_err
)));
811 return ccb
->callback(commit_info
, ccb
->callback_baton
, ccb
->pool
);
815 static svn_error_t
*ra_svn_commit(svn_ra_session_t
*session
,
816 const svn_delta_editor_t
**editor
,
818 apr_hash_t
*revprop_table
,
819 svn_commit_callback2_t callback
,
820 void *callback_baton
,
821 apr_hash_t
*lock_tokens
,
822 svn_boolean_t keep_locks
,
825 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
826 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
827 ra_svn_commit_callback_baton_t
*ccb
;
828 apr_hash_index_t
*hi
;
829 apr_pool_t
*iterpool
;
830 const svn_string_t
*log_msg
= apr_hash_get(revprop_table
,
831 SVN_PROP_REVISION_LOG
,
832 APR_HASH_KEY_STRING
);
834 /* If we're sending revprops other than svn:log, make sure the server won't
835 silently ignore them. */
836 if (apr_hash_count(revprop_table
) > 1 &&
837 ! svn_ra_svn_has_capability(conn
, SVN_RA_SVN_CAP_COMMIT_REVPROPS
))
838 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
,
839 _("Server doesn't support setting arbitrary "
840 "revision properties during commit"));
842 /* Tell the server we're starting the commit.
843 Send log message here for backwards compatibility with servers
845 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c(!", "commit",
849 iterpool
= svn_pool_create(pool
);
850 for (hi
= apr_hash_first(pool
, lock_tokens
); hi
; hi
= apr_hash_next(hi
))
854 const char *path
, *token
;
855 svn_pool_clear(iterpool
);
856 apr_hash_this(hi
, &key
, NULL
, &val
);
859 SVN_ERR(svn_ra_svn_write_tuple(conn
, iterpool
, "cc", path
, token
));
861 svn_pool_destroy(iterpool
);
863 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)b(!", keep_locks
));
864 SVN_ERR(svn_ra_svn_write_proplist(conn
, pool
, revprop_table
));
865 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
866 SVN_ERR(handle_auth_request(sess_baton
, pool
));
867 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
869 /* Remember a few arguments for when the commit is over. */
870 ccb
= apr_palloc(pool
, sizeof(*ccb
));
871 ccb
->sess_baton
= sess_baton
;
873 ccb
->callback
= callback
;
874 ccb
->callback_baton
= callback_baton
;
876 /* Fetch an editor for the caller to drive. The editor will call
877 * ra_svn_end_commit() upon close_edit(), at which point we'll fill
878 * in the new_rev, committed_date, and committed_author values. */
879 svn_ra_svn_get_editor(editor
, edit_baton
, conn
, pool
,
880 ra_svn_end_commit
, ccb
);
884 static svn_error_t
*ra_svn_get_file(svn_ra_session_t
*session
, const char *path
,
885 svn_revnum_t rev
, svn_stream_t
*stream
,
886 svn_revnum_t
*fetched_rev
,
890 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
891 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
892 apr_array_header_t
*proplist
;
893 unsigned char digest
[APR_MD5_DIGESTSIZE
];
894 const char *expected_checksum
, *hex_digest
;
895 apr_md5_ctx_t md5_context
;
896 apr_pool_t
*iterpool
;
898 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-file", "c(?r)bb", path
,
899 rev
, (props
!= NULL
), (stream
!= NULL
)));
900 SVN_ERR(handle_auth_request(sess_baton
, pool
));
901 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?c)rl",
908 SVN_ERR(svn_ra_svn_parse_proplist(proplist
, pool
, props
));
910 /* We're done if the contents weren't wanted. */
914 if (expected_checksum
)
915 apr_md5_init(&md5_context
);
917 /* Read the file's contents. */
918 iterpool
= svn_pool_create(pool
);
921 svn_ra_svn_item_t
*item
;
923 svn_pool_clear(iterpool
);
924 SVN_ERR(svn_ra_svn_read_item(conn
, iterpool
, &item
));
925 if (item
->kind
!= SVN_RA_SVN_STRING
)
926 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
927 _("Non-string as part of file contents"));
928 if (item
->u
.string
->len
== 0)
931 if (expected_checksum
)
932 apr_md5_update(&md5_context
, item
->u
.string
->data
,
933 item
->u
.string
->len
);
935 SVN_ERR(svn_stream_write(stream
, item
->u
.string
->data
,
936 &item
->u
.string
->len
));
938 svn_pool_destroy(iterpool
);
940 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
942 if (expected_checksum
)
944 apr_md5_final(digest
, &md5_context
);
945 hex_digest
= svn_md5_digest_to_cstring_display(digest
, pool
);
946 if (strcmp(hex_digest
, expected_checksum
) != 0)
947 return svn_error_createf
948 (SVN_ERR_CHECKSUM_MISMATCH
, NULL
,
949 _("Checksum mismatch for '%s':\n"
950 " expected checksum: %s\n"
951 " actual checksum: %s\n"),
952 path
, expected_checksum
, hex_digest
);
958 static svn_error_t
*ra_svn_get_dir(svn_ra_session_t
*session
,
959 apr_hash_t
**dirents
,
960 svn_revnum_t
*fetched_rev
,
964 apr_uint32_t dirent_fields
,
967 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
968 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
970 apr_array_header_t
*proplist
, *dirlist
;
972 svn_ra_svn_item_t
*elt
;
973 const char *name
, *kind
, *cdate
, *cauthor
;
974 svn_boolean_t has_props
;
976 svn_dirent_t
*dirent
;
978 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c(?r)bb(!", "get-dir", path
,
979 rev
, (props
!= NULL
), (dirents
!= NULL
)));
980 if (dirent_fields
& SVN_DIRENT_KIND
)
982 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, SVN_RA_SVN_DIRENT_KIND
));
984 if (dirent_fields
& SVN_DIRENT_SIZE
)
986 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, SVN_RA_SVN_DIRENT_SIZE
));
988 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
990 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, SVN_RA_SVN_DIRENT_HAS_PROPS
));
992 if (dirent_fields
& SVN_DIRENT_CREATED_REV
)
994 SVN_ERR(svn_ra_svn_write_word(conn
, pool
,
995 SVN_RA_SVN_DIRENT_CREATED_REV
));
997 if (dirent_fields
& SVN_DIRENT_TIME
)
999 SVN_ERR(svn_ra_svn_write_word(conn
, pool
, SVN_RA_SVN_DIRENT_TIME
));
1001 if (dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
1003 SVN_ERR(svn_ra_svn_write_word(conn
, pool
,
1004 SVN_RA_SVN_DIRENT_LAST_AUTHOR
));
1006 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1008 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1009 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "rll", &rev
, &proplist
,
1015 SVN_ERR(svn_ra_svn_parse_proplist(proplist
, pool
, props
));
1017 /* We're done if dirents aren't wanted. */
1019 return SVN_NO_ERROR
;
1021 /* Interpret the directory list. */
1022 *dirents
= apr_hash_make(pool
);
1023 for (i
= 0; i
< dirlist
->nelts
; i
++)
1025 elt
= &APR_ARRAY_IDX(dirlist
, i
, svn_ra_svn_item_t
);
1026 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1027 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1028 _("Dirlist element not a list"));
1029 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, pool
, "cwnbr(?c)(?c)",
1030 &name
, &kind
, &size
, &has_props
,
1031 &crev
, &cdate
, &cauthor
));
1032 name
= svn_path_canonicalize(name
, pool
);
1033 dirent
= apr_palloc(pool
, sizeof(*dirent
));
1034 SVN_ERR(interpret_kind(kind
, pool
, &dirent
->kind
));
1035 dirent
->size
= size
;/* FIXME: svn_filesize_t */
1036 dirent
->has_props
= has_props
;
1037 dirent
->created_rev
= crev
;
1038 SVN_ERR(svn_time_from_cstring(&dirent
->time
, cdate
, pool
));
1039 dirent
->last_author
= cauthor
;
1040 apr_hash_set(*dirents
, name
, APR_HASH_KEY_STRING
, dirent
);
1043 return SVN_NO_ERROR
;
1046 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1047 server, which defaults to youngest. */
1048 static svn_error_t
*ra_svn_get_mergeinfo(svn_ra_session_t
*session
,
1049 apr_hash_t
**mergeinfo
,
1050 const apr_array_header_t
*paths
,
1051 svn_revnum_t revision
,
1052 svn_mergeinfo_inheritance_t inherit
,
1053 svn_boolean_t include_descendants
,
1056 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1057 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1059 apr_array_header_t
*mergeinfo_tuple
;
1060 svn_ra_svn_item_t
*elt
;
1061 const char *path
, *to_parse
;
1062 apr_hash_t
*for_path
;
1064 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "get-mergeinfo"));
1065 for (i
= 0; i
< paths
->nelts
; i
++)
1067 path
= APR_ARRAY_IDX(paths
, i
, const char *);
1068 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, path
));
1070 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)(?r)wb)", revision
,
1071 svn_inheritance_to_word(inherit
),
1072 include_descendants
));
1074 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1075 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &mergeinfo_tuple
));
1078 if (mergeinfo_tuple
!= NULL
&& mergeinfo_tuple
->nelts
> 0)
1080 *mergeinfo
= apr_hash_make(pool
);
1081 for (i
= 0; i
< mergeinfo_tuple
->nelts
; i
++)
1083 elt
= &((svn_ra_svn_item_t
*) mergeinfo_tuple
->elts
)[i
];
1084 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1085 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1086 _("Mergeinfo element is not a list"));
1087 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, pool
, "cc",
1089 SVN_ERR(svn_mergeinfo_parse(&for_path
, to_parse
, pool
));
1090 apr_hash_set(*mergeinfo
, path
, APR_HASH_KEY_STRING
, for_path
);
1094 return SVN_NO_ERROR
;
1097 static svn_error_t
*ra_svn_update(svn_ra_session_t
*session
,
1098 const svn_ra_reporter3_t
**reporter
,
1099 void **report_baton
, svn_revnum_t rev
,
1100 const char *target
, svn_depth_t depth
,
1101 svn_boolean_t send_copyfrom_args
,
1102 const svn_delta_editor_t
*update_editor
,
1103 void *update_baton
, apr_pool_t
*pool
)
1105 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1106 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1107 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1109 /* Tell the server we want to start an update. */
1110 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "update", "(?r)cbwb", rev
, target
,
1111 recurse
, svn_depth_to_word(depth
),
1112 send_copyfrom_args
));
1113 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1115 /* Fetch a reporter for the caller to drive. The reporter will drive
1116 * update_editor upon finish_report(). */
1117 ra_svn_get_reporter(sess_baton
, pool
, update_editor
, update_baton
,
1118 target
, depth
, reporter
, report_baton
);
1119 return SVN_NO_ERROR
;
1122 static svn_error_t
*ra_svn_switch(svn_ra_session_t
*session
,
1123 const svn_ra_reporter3_t
**reporter
,
1124 void **report_baton
, svn_revnum_t rev
,
1125 const char *target
, svn_depth_t depth
,
1126 const char *switch_url
,
1127 const svn_delta_editor_t
*update_editor
,
1128 void *update_baton
, apr_pool_t
*pool
)
1130 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1131 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1132 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1134 /* Tell the server we want to start a switch. */
1135 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "switch", "(?r)cbcw", rev
,
1136 target
, recurse
, switch_url
,
1137 svn_depth_to_word(depth
)));
1138 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1140 /* Fetch a reporter for the caller to drive. The reporter will drive
1141 * update_editor upon finish_report(). */
1142 ra_svn_get_reporter(sess_baton
, pool
, update_editor
, update_baton
,
1143 target
, depth
, reporter
, report_baton
);
1144 return SVN_NO_ERROR
;
1147 static svn_error_t
*ra_svn_status(svn_ra_session_t
*session
,
1148 const svn_ra_reporter3_t
**reporter
,
1149 void **report_baton
,
1150 const char *target
, svn_revnum_t rev
,
1152 const svn_delta_editor_t
*status_editor
,
1153 void *status_baton
, apr_pool_t
*pool
)
1155 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1156 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1157 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1159 /* Tell the server we want to start a status operation. */
1160 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "status", "cb(?r)w",
1161 target
, recurse
, rev
,
1162 svn_depth_to_word(depth
)));
1163 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1165 /* Fetch a reporter for the caller to drive. The reporter will drive
1166 * status_editor upon finish_report(). */
1167 ra_svn_get_reporter(sess_baton
, pool
, status_editor
, status_baton
,
1168 target
, depth
, reporter
, report_baton
);
1169 return SVN_NO_ERROR
;
1172 static svn_error_t
*ra_svn_diff(svn_ra_session_t
*session
,
1173 const svn_ra_reporter3_t
**reporter
,
1174 void **report_baton
,
1175 svn_revnum_t rev
, const char *target
,
1177 svn_boolean_t ignore_ancestry
,
1178 svn_boolean_t text_deltas
,
1179 const char *versus_url
,
1180 const svn_delta_editor_t
*diff_editor
,
1181 void *diff_baton
, apr_pool_t
*pool
)
1183 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1184 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1185 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1187 /* Tell the server we want to start a diff. */
1188 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "diff", "(?r)cbbcbw", rev
,
1189 target
, recurse
, ignore_ancestry
,
1190 versus_url
, text_deltas
,
1191 svn_depth_to_word(depth
)));
1192 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1194 /* Fetch a reporter for the caller to drive. The reporter will drive
1195 * diff_editor upon finish_report(). */
1196 ra_svn_get_reporter(sess_baton
, pool
, diff_editor
, diff_baton
,
1197 target
, depth
, reporter
, report_baton
);
1198 return SVN_NO_ERROR
;
1201 static svn_error_t
*ra_svn_log(svn_ra_session_t
*session
,
1202 const apr_array_header_t
*paths
,
1203 svn_revnum_t start
, svn_revnum_t end
,
1205 svn_boolean_t discover_changed_paths
,
1206 svn_boolean_t strict_node_history
,
1207 svn_boolean_t include_merged_revisions
,
1208 const apr_array_header_t
*revprops
,
1209 svn_log_entry_receiver_t receiver
,
1210 void *receiver_baton
, apr_pool_t
*pool
)
1212 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1213 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1214 apr_pool_t
*subpool
;
1216 const char *path
, *cpath
, *action
, *copy_path
;
1217 svn_string_t
*author
, *date
, *message
;
1218 svn_ra_svn_item_t
*item
, *elt
;
1220 apr_array_header_t
*cplist
, *rplist
;
1222 svn_revnum_t rev
, copy_rev
;
1223 svn_log_changed_path_t
*change
;
1225 apr_uint64_t has_children_param
, invalid_revnum_param
;
1226 svn_boolean_t has_children
;
1227 svn_log_entry_t
*log_entry
;
1228 svn_boolean_t want_custom_revprops
;
1229 apr_uint64_t revprop_count
;
1231 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "log"));
1234 for (i
= 0; i
< paths
->nelts
; i
++)
1236 path
= APR_ARRAY_IDX(paths
, i
, const char *);
1237 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, path
));
1240 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)(?r)(?r)bbnb!", start
, end
,
1241 discover_changed_paths
, strict_node_history
,
1242 (apr_uint64_t
) limit
,
1243 include_merged_revisions
));
1246 want_custom_revprops
= FALSE
;
1247 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!w(!", "revprops"));
1248 for (i
= 0; i
< revprops
->nelts
; i
++)
1250 name
= APR_ARRAY_IDX(revprops
, i
, char *);
1251 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, name
));
1252 if (!want_custom_revprops
1253 && strcmp(name
, SVN_PROP_REVISION_AUTHOR
) != 0
1254 && strcmp(name
, SVN_PROP_REVISION_DATE
) != 0
1255 && strcmp(name
, SVN_PROP_REVISION_LOG
) != 0)
1256 want_custom_revprops
= TRUE
;
1258 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1262 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!w())", "all-revprops"));
1263 want_custom_revprops
= TRUE
;
1266 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1268 /* Read the log messages. */
1269 subpool
= svn_pool_create(pool
);
1272 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &item
));
1273 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1275 if (item
->kind
!= SVN_RA_SVN_LIST
)
1276 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1277 _("Log entry not a list"));
1278 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, subpool
,
1279 "lr(?s)(?s)(?s)?BBnl",
1280 &cplist
, &rev
, &author
, &date
,
1281 &message
, &has_children_param
,
1282 &invalid_revnum_param
,
1283 &revprop_count
, &rplist
));
1284 if (want_custom_revprops
&& rplist
== NULL
)
1286 /* Caller asked for custom revprops, but server is too old. */
1287 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
,
1288 _("Server does not support custom revprops"
1292 if (has_children_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
1293 has_children
= FALSE
;
1295 has_children
= (svn_boolean_t
) has_children_param
;
1297 /* Because the svn protocol won't let us send an invalid revnum, we have
1298 to recover that fact using the extra parameter. */
1299 if (invalid_revnum_param
!= SVN_RA_SVN_UNSPECIFIED_NUMBER
1300 && invalid_revnum_param
== TRUE
)
1301 rev
= SVN_INVALID_REVNUM
;
1303 if (cplist
->nelts
> 0)
1305 /* Interpret the changed-paths list. */
1306 cphash
= apr_hash_make(subpool
);
1307 for (i
= 0; i
< cplist
->nelts
; i
++)
1309 elt
= &APR_ARRAY_IDX(cplist
, i
, svn_ra_svn_item_t
);
1310 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1311 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1312 _("Changed-path entry not a list"));
1313 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, subpool
, "cw(?cr)",
1314 &cpath
, &action
, ©_path
,
1316 cpath
= svn_path_canonicalize(cpath
, subpool
);
1318 copy_path
= svn_path_canonicalize(copy_path
, subpool
);
1319 change
= apr_palloc(subpool
, sizeof(*change
));
1320 change
->action
= *action
;
1321 change
->copyfrom_path
= copy_path
;
1322 change
->copyfrom_rev
= copy_rev
;
1323 apr_hash_set(cphash
, cpath
, APR_HASH_KEY_STRING
, change
);
1329 if (! (limit
&& ++nreceived
> limit
))
1331 log_entry
= svn_log_entry_create(subpool
);
1333 log_entry
->changed_paths
= cphash
;
1334 log_entry
->revision
= rev
;
1335 log_entry
->has_children
= has_children
;
1337 SVN_ERR(svn_ra_svn_parse_proplist(rplist
, pool
,
1338 &log_entry
->revprops
));
1339 if (log_entry
->revprops
== NULL
)
1340 log_entry
->revprops
= apr_hash_make(pool
);
1341 if (revprops
== NULL
)
1343 /* Caller requested all revprops; set author/date/log. */
1345 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_AUTHOR
,
1346 APR_HASH_KEY_STRING
, author
);
1348 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_DATE
,
1349 APR_HASH_KEY_STRING
, date
);
1351 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_LOG
,
1352 APR_HASH_KEY_STRING
, message
);
1356 /* Caller requested some; maybe set author/date/log. */
1357 for (i
= 0; i
< revprops
->nelts
; i
++)
1359 name
= APR_ARRAY_IDX(revprops
, i
, char *);
1360 if (author
&& strcmp(name
, SVN_PROP_REVISION_AUTHOR
) == 0)
1361 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_AUTHOR
,
1362 APR_HASH_KEY_STRING
, author
);
1363 if (date
&& strcmp(name
, SVN_PROP_REVISION_DATE
) == 0)
1364 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_DATE
,
1365 APR_HASH_KEY_STRING
, date
);
1366 if (message
&& strcmp(name
, SVN_PROP_REVISION_LOG
) == 0)
1367 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_LOG
,
1368 APR_HASH_KEY_STRING
, message
);
1371 SVN_ERR(receiver(receiver_baton
, log_entry
, subpool
));
1373 svn_pool_clear(subpool
);
1375 svn_pool_destroy(subpool
);
1377 /* Read the response. */
1378 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1380 return SVN_NO_ERROR
;
1384 static svn_error_t
*ra_svn_check_path(svn_ra_session_t
*session
,
1385 const char *path
, svn_revnum_t rev
,
1386 svn_node_kind_t
*kind
, apr_pool_t
*pool
)
1388 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1389 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1390 const char *kind_word
;
1392 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "check-path", "c(?r)", path
, rev
));
1393 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1394 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "w", &kind_word
));
1395 SVN_ERR(interpret_kind(kind_word
, pool
, kind
));
1396 return SVN_NO_ERROR
;
1400 /* If ERR is a command not supported error, wrap it in a
1401 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1402 static svn_error_t
*handle_unsupported_cmd(svn_error_t
*err
,
1405 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1406 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, err
,
1412 static svn_error_t
*ra_svn_stat(svn_ra_session_t
*session
,
1413 const char *path
, svn_revnum_t rev
,
1414 svn_dirent_t
**dirent
, apr_pool_t
*pool
)
1416 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1417 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1418 apr_array_header_t
*list
= NULL
;
1419 const char *kind
, *cdate
, *cauthor
;
1421 svn_boolean_t has_props
;
1423 svn_dirent_t
*the_dirent
;
1425 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "stat", "c(?r)", path
, rev
));
1427 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1428 _("'stat' not implemented")));
1430 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &list
));
1438 SVN_ERR(svn_ra_svn_parse_tuple(list
, pool
, "wnbr(?c)(?c)",
1439 &kind
, &size
, &has_props
,
1440 &crev
, &cdate
, &cauthor
));
1442 the_dirent
= apr_palloc(pool
, sizeof(*the_dirent
));
1443 SVN_ERR(interpret_kind(kind
, pool
, &the_dirent
->kind
));
1444 the_dirent
->size
= size
;/* FIXME: svn_filesize_t */
1445 the_dirent
->has_props
= has_props
;
1446 the_dirent
->created_rev
= crev
;
1447 SVN_ERR(svn_time_from_cstring(&the_dirent
->time
, cdate
, pool
));
1448 the_dirent
->last_author
= cauthor
;
1450 *dirent
= the_dirent
;
1453 return SVN_NO_ERROR
;
1457 static svn_error_t
*ra_svn_get_locations(svn_ra_session_t
*session
,
1458 apr_hash_t
**locations
,
1460 svn_revnum_t peg_revision
,
1461 apr_array_header_t
*location_revisions
,
1464 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1465 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1466 svn_revnum_t revision
;
1467 svn_ra_svn_item_t
*item
;
1468 svn_boolean_t is_done
;
1470 const char *ret_path
;
1472 /* Transmit the parameters. */
1473 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(cr(!",
1474 "get-locations", path
, peg_revision
));
1475 for (i
= 0; i
< location_revisions
->nelts
; i
++)
1477 revision
= APR_ARRAY_IDX(location_revisions
, i
, svn_revnum_t
);
1478 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!r!", revision
));
1481 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1483 /* Servers before 1.1 don't support this command. Check for this here. */
1484 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1485 _("'get-locations' not implemented")));
1487 /* Read the hash items. */
1489 *locations
= apr_hash_make(pool
);
1492 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &item
));
1493 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1495 else if (item
->kind
!= SVN_RA_SVN_LIST
)
1496 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1497 _("Location entry not a list"));
1500 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, pool
, "rc",
1501 &revision
, &ret_path
));
1502 ret_path
= svn_path_canonicalize(ret_path
, pool
);
1503 apr_hash_set(*locations
, apr_pmemdup(pool
, &revision
,
1505 sizeof(revision
), ret_path
);
1509 /* Read the response. This is so the server would have a chance to
1510 * report an error. */
1511 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1513 return SVN_NO_ERROR
;
1516 static svn_error_t
*
1517 ra_svn_get_location_segments(svn_ra_session_t
*session
,
1519 svn_revnum_t peg_revision
,
1520 svn_revnum_t start_rev
,
1521 svn_revnum_t end_rev
,
1522 svn_location_segment_receiver_t receiver
,
1523 void *receiver_baton
,
1526 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1527 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1528 svn_ra_svn_item_t
*item
;
1529 svn_boolean_t is_done
;
1530 svn_revnum_t range_start
, range_end
;
1531 const char *ret_path
;
1532 svn_location_segment_t
*segment
;
1533 apr_pool_t
*subpool
= svn_pool_create(pool
);
1535 /* Transmit the parameters. */
1536 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c(?r)(?r)(?r))",
1537 "get-location-segments",
1538 path
, peg_revision
, start_rev
, end_rev
));
1540 /* Servers before 1.1 don't support this command. Check for this here. */
1541 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1542 _("'get-location-segments' not implemented")));
1544 /* Parse the response. */
1548 svn_pool_clear(subpool
);
1549 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &item
));
1550 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1552 else if (item
->kind
!= SVN_RA_SVN_LIST
)
1553 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1554 _("Location segment entry not a list"));
1557 segment
= apr_pcalloc(subpool
, sizeof(*segment
));
1558 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, subpool
, "rr(?c)",
1559 &range_start
, &range_end
, &ret_path
));
1560 if (! (SVN_IS_VALID_REVNUM(range_start
)
1561 && SVN_IS_VALID_REVNUM(range_end
)))
1562 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1563 _("Expected valid revision range"));
1565 ret_path
= svn_path_canonicalize(ret_path
, subpool
);
1566 segment
->path
= ret_path
;
1567 segment
->range_start
= range_start
;
1568 segment
->range_end
= range_end
;
1569 SVN_ERR(receiver(segment
, receiver_baton
, subpool
));
1572 svn_pool_destroy(subpool
);
1574 /* Read the response. This is so the server would have a chance to
1575 * report an error. */
1576 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1578 return SVN_NO_ERROR
;
1581 static svn_error_t
*ra_svn_get_file_revs(svn_ra_session_t
*session
,
1583 svn_revnum_t start
, svn_revnum_t end
,
1584 svn_boolean_t include_merged_revisions
,
1585 svn_file_rev_handler_t handler
,
1586 void *handler_baton
, apr_pool_t
*pool
)
1588 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1589 apr_pool_t
*rev_pool
, *chunk_pool
;
1590 svn_ra_svn_item_t
*item
;
1593 apr_array_header_t
*rev_proplist
, *proplist
;
1594 apr_hash_t
*rev_props
;
1595 apr_array_header_t
*props
;
1596 svn_boolean_t has_txdelta
;
1597 svn_boolean_t had_revision
= FALSE
;
1598 svn_stream_t
*stream
;
1599 svn_txdelta_window_handler_t d_handler
;
1602 apr_uint64_t merged_rev_param
;
1603 svn_boolean_t merged_rev
;
1605 /* One sub-pool for each revision and one for each txdelta chunk.
1606 Note that the rev_pool must live during the following txdelta. */
1607 rev_pool
= svn_pool_create(pool
);
1608 chunk_pool
= svn_pool_create(pool
);
1610 SVN_ERR(svn_ra_svn_write_cmd(sess_baton
->conn
, pool
, "get-file-revs",
1611 "c(?r)(?r)b", path
, start
, end
,
1612 include_merged_revisions
));
1614 /* Servers before 1.1 don't support this command. Check for this here. */
1615 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1616 _("'get-file-revs' not implemented")));
1620 svn_pool_clear(rev_pool
);
1621 svn_pool_clear(chunk_pool
);
1622 SVN_ERR(svn_ra_svn_read_item(sess_baton
->conn
, rev_pool
, &item
));
1623 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1625 /* Either we've got a correct revision or we will error out below. */
1626 had_revision
= TRUE
;
1627 if (item
->kind
!= SVN_RA_SVN_LIST
)
1628 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1629 _("Revision entry not a list"));
1631 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, rev_pool
,
1632 "crll?B", &p
, &rev
, &rev_proplist
,
1633 &proplist
, &merged_rev_param
));
1634 p
= svn_path_canonicalize(p
, rev_pool
);
1635 SVN_ERR(svn_ra_svn_parse_proplist(rev_proplist
, rev_pool
, &rev_props
));
1636 SVN_ERR(parse_prop_diffs(proplist
, rev_pool
, &props
));
1637 if (merged_rev_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
1640 merged_rev
= (svn_boolean_t
) merged_rev_param
;
1642 /* Get the first delta chunk so we know if there is a delta. */
1643 SVN_ERR(svn_ra_svn_read_item(sess_baton
->conn
, chunk_pool
, &item
));
1644 if (item
->kind
!= SVN_RA_SVN_STRING
)
1645 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1646 _("Text delta chunk not a string"));
1647 has_txdelta
= item
->u
.string
->len
> 0;
1649 SVN_ERR(handler(handler_baton
, p
, rev
, rev_props
, merged_rev
,
1650 has_txdelta
? &d_handler
: NULL
, &d_baton
,
1653 /* Process the text delta if any. */
1657 stream
= svn_txdelta_parse_svndiff(d_handler
, d_baton
, TRUE
,
1661 while (item
->u
.string
->len
> 0)
1663 size
= item
->u
.string
->len
;
1665 SVN_ERR(svn_stream_write(stream
, item
->u
.string
->data
, &size
));
1666 svn_pool_clear(chunk_pool
);
1668 SVN_ERR(svn_ra_svn_read_item(sess_baton
->conn
, chunk_pool
, &item
));
1669 if (item
->kind
!= SVN_RA_SVN_STRING
)
1670 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1671 _("Text delta chunk not a string"));
1674 SVN_ERR(svn_stream_close(stream
));
1678 SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton
->conn
, pool
, ""));
1680 /* Return error if we didn't get any revisions. */
1682 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1683 _("The get-file-revs command didn't return "
1686 svn_pool_destroy(chunk_pool
);
1687 svn_pool_destroy(rev_pool
);
1689 return SVN_NO_ERROR
;
1692 /* For each path in PATH_REVS, send a 'lock' command to the server.
1693 Used with 1.2.x series servers which support locking, but of only
1694 one path at a time. ra_svn_lock(), which supports 'lock-many'
1695 is now the default. See svn_ra_lock() docstring for interface details. */
1696 static svn_error_t
*ra_svn_lock_compat(svn_ra_session_t
*session
,
1697 apr_hash_t
*path_revs
,
1698 const char *comment
,
1699 svn_boolean_t steal_lock
,
1700 svn_ra_lock_callback_t lock_func
,
1704 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1705 svn_ra_svn_conn_t
* conn
= sess
->conn
;
1706 apr_array_header_t
*list
;
1707 apr_hash_index_t
*hi
;
1708 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1710 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1716 svn_revnum_t
*revnum
;
1717 svn_error_t
*err
, *callback_err
= NULL
;
1719 svn_pool_clear(iterpool
);
1721 apr_hash_this(hi
, &key
, NULL
, &val
);
1725 SVN_ERR(svn_ra_svn_write_cmd(conn
, iterpool
, "lock", "c(?c)b(?r)",
1727 steal_lock
, *revnum
));
1729 /* Servers before 1.2 doesn't support locking. Check this here. */
1730 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
1731 _("Server doesn't support "
1732 "the lock command")));
1734 err
= svn_ra_svn_read_cmd_response(conn
, iterpool
, "l", &list
);
1737 SVN_ERR(parse_lock(list
, iterpool
, &lock
));
1739 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
1743 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
1746 svn_error_clear(err
);
1749 return callback_err
;
1752 svn_pool_destroy(iterpool
);
1754 return SVN_NO_ERROR
;
1757 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
1758 Used with 1.2.x series servers which support unlocking, but of only
1759 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
1760 now the default. See svn_ra_unlock() docstring for interface details. */
1761 static svn_error_t
*ra_svn_unlock_compat(svn_ra_session_t
*session
,
1762 apr_hash_t
*path_tokens
,
1763 svn_boolean_t break_lock
,
1764 svn_ra_lock_callback_t lock_func
,
1768 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1769 svn_ra_svn_conn_t
* conn
= sess
->conn
;
1770 apr_hash_index_t
*hi
;
1771 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1773 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1779 svn_error_t
*err
, *callback_err
= NULL
;
1781 svn_pool_clear(iterpool
);
1783 apr_hash_this(hi
, &key
, NULL
, &val
);
1785 if (strcmp(val
, "") != 0)
1790 SVN_ERR(svn_ra_svn_write_cmd(conn
, iterpool
, "unlock", "c(?c)b",
1791 path
, token
, break_lock
));
1793 /* Servers before 1.2 don't support locking. Check this here. */
1794 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, iterpool
),
1795 _("Server doesn't support the unlock "
1798 err
= svn_ra_svn_read_cmd_response(conn
, iterpool
, "");
1800 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
1804 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, pool
);
1806 svn_error_clear(err
);
1809 return callback_err
;
1812 svn_pool_destroy(iterpool
);
1814 return SVN_NO_ERROR
;
1817 /* Tell the server to lock all paths in PATH_REVS.
1818 See svn_ra_lock() for interface details. */
1819 static svn_error_t
*ra_svn_lock(svn_ra_session_t
*session
,
1820 apr_hash_t
*path_revs
,
1821 const char *comment
,
1822 svn_boolean_t steal_lock
,
1823 svn_ra_lock_callback_t lock_func
,
1827 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1828 svn_ra_svn_conn_t
*conn
= sess
->conn
;
1829 apr_hash_index_t
*hi
;
1830 svn_ra_svn_item_t
*elt
;
1831 svn_error_t
*err
, *callback_err
= SVN_NO_ERROR
;
1832 apr_pool_t
*subpool
= svn_pool_create(pool
);
1835 apr_array_header_t
*list
= NULL
;
1837 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((?c)b(!", "lock-many",
1838 comment
, steal_lock
));
1840 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1845 svn_revnum_t
*revnum
;
1847 svn_pool_clear(subpool
);
1848 apr_hash_this(hi
, &key
, NULL
, &val
);
1852 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "c(?r)", path
, *revnum
));
1855 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1857 err
= handle_auth_request(sess
, pool
);
1859 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
1861 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1863 svn_error_clear(err
);
1864 return ra_svn_lock_compat(session
, path_revs
, comment
, steal_lock
,
1865 lock_func
, lock_baton
, pool
);
1871 /* Loop over responses to get lock information. */
1872 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1877 apr_hash_this(hi
, &key
, NULL
, NULL
);
1880 svn_pool_clear(subpool
);
1881 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &elt
));
1883 /* The server might have encountered some sort of fatal error in
1884 the middle of the request list. If this happens, it will
1885 transmit "done" to end the lock-info early, and then the
1886 overall command response will talk about the fatal error. */
1887 if (elt
->kind
== SVN_RA_SVN_WORD
&& strcmp(elt
->u
.word
, "done") == 0)
1890 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1891 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1892 _("Lock response not a list"));
1894 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, subpool
, "wl", &status
,
1897 if (strcmp(status
, "failure") == 0)
1898 err
= svn_ra_svn__handle_failure_status(list
, subpool
);
1899 else if (strcmp(status
, "success") == 0)
1901 SVN_ERR(parse_lock(list
, subpool
, &lock
));
1905 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1906 _("Unknown status for lock command"));
1909 callback_err
= lock_func(lock_baton
, path
, TRUE
,
1913 callback_err
= SVN_NO_ERROR
;
1915 svn_error_clear(err
);
1918 return callback_err
;
1921 /* If we didn't break early above, and the whole hash was traversed,
1922 read the final "done" from the server. */
1925 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &elt
));
1926 if (elt
->kind
!= SVN_RA_SVN_WORD
|| strcmp(elt
->u
.word
, "done") != 0)
1927 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1928 _("Didn't receive end marker for lock "
1932 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1934 svn_pool_destroy(subpool
);
1936 return SVN_NO_ERROR
;
1939 /* Tell the server to unlock all paths in PATH_TOKENS.
1940 See svn_ra_unlock() for interface details. */
1941 static svn_error_t
*ra_svn_unlock(svn_ra_session_t
*session
,
1942 apr_hash_t
*path_tokens
,
1943 svn_boolean_t break_lock
,
1944 svn_ra_lock_callback_t lock_func
,
1948 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1949 svn_ra_svn_conn_t
*conn
= sess
->conn
;
1950 apr_hash_index_t
*hi
;
1951 apr_pool_t
*subpool
= svn_pool_create(pool
);
1952 svn_error_t
*err
, *callback_err
= NULL
;
1953 svn_ra_svn_item_t
*elt
;
1954 const char *status
= NULL
;
1955 apr_array_header_t
*list
= NULL
;
1959 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(b(!", "unlock-many",
1962 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1967 svn_pool_clear(subpool
);
1968 apr_hash_this(hi
, &key
, NULL
, &val
);
1971 if (strcmp(val
, "") != 0)
1976 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "c(?c)", path
, token
));
1979 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1981 err
= handle_auth_request(sess
, pool
);
1983 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
1986 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1988 svn_error_clear(err
);
1989 return ra_svn_unlock_compat(session
, path_tokens
, break_lock
, lock_func
,
1996 /* Loop over responses to unlock files. */
1997 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1999 svn_pool_clear(subpool
);
2001 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &elt
));
2003 /* The server might have encountered some sort of fatal error in
2004 the middle of the request list. If this happens, it will
2005 transmit "done" to end the lock-info early, and then the
2006 overall command response will talk about the fatal error. */
2007 if (elt
->kind
== SVN_RA_SVN_WORD
&& (strcmp(elt
->u
.word
, "done") == 0))
2010 apr_hash_this(hi
, &key
, NULL
, NULL
);
2013 if (elt
->kind
!= SVN_RA_SVN_LIST
)
2014 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2015 _("Unlock response not a list"));
2017 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, subpool
, "wl", &status
,
2020 if (strcmp(status
, "failure") == 0)
2021 err
= svn_ra_svn__handle_failure_status(list
, subpool
);
2022 else if (strcmp(status
, "success") == 0)
2024 SVN_ERR(svn_ra_svn_parse_tuple(list
, subpool
, "c", &path
));
2028 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2029 _("Unknown status for unlock command"));
2032 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
,
2035 callback_err
= SVN_NO_ERROR
;
2037 svn_error_clear(err
);
2040 return callback_err
;
2043 /* If we didn't break early above, and the whole hash was traversed,
2044 read the final "done" from the server. */
2047 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &elt
));
2048 if (elt
->kind
!= SVN_RA_SVN_WORD
|| strcmp(elt
->u
.word
, "done") != 0)
2049 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2050 _("Didn't receive end marker for unlock "
2054 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
2056 svn_pool_destroy(subpool
);
2058 return SVN_NO_ERROR
;
2061 static svn_error_t
*ra_svn_get_lock(svn_ra_session_t
*session
,
2066 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2067 svn_ra_svn_conn_t
* conn
= sess
->conn
;
2068 apr_array_header_t
*list
;
2070 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-lock", "c", path
));
2072 /* Servers before 1.2 doesn't support locking. Check this here. */
2073 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2074 _("Server doesn't support the get-lock "
2077 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &list
));
2079 SVN_ERR(parse_lock(list
, pool
, lock
));
2083 return SVN_NO_ERROR
;
2086 static svn_error_t
*ra_svn_get_locks(svn_ra_session_t
*session
,
2091 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2092 svn_ra_svn_conn_t
* conn
= sess
->conn
;
2093 apr_array_header_t
*list
;
2095 svn_ra_svn_item_t
*elt
;
2098 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-locks", "c", path
));
2100 /* Servers before 1.2 doesn't support locking. Check this here. */
2101 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2102 _("Server doesn't support the get-lock "
2105 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "l", &list
));
2107 *locks
= apr_hash_make(pool
);
2109 for (i
= 0; i
< list
->nelts
; ++i
)
2111 elt
= &APR_ARRAY_IDX(list
, i
, svn_ra_svn_item_t
);
2113 if (elt
->kind
!= SVN_RA_SVN_LIST
)
2114 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2115 _("Lock element not a list"));
2116 SVN_ERR(parse_lock(elt
->u
.list
, pool
, &lock
));
2117 apr_hash_set(*locks
, lock
->path
, APR_HASH_KEY_STRING
, lock
);
2120 return SVN_NO_ERROR
;
2124 static svn_error_t
*ra_svn_replay(svn_ra_session_t
*session
,
2125 svn_revnum_t revision
,
2126 svn_revnum_t low_water_mark
,
2127 svn_boolean_t send_deltas
,
2128 const svn_delta_editor_t
*editor
,
2132 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2134 SVN_ERR(svn_ra_svn_write_cmd(sess
->conn
, pool
, "replay", "rrb", revision
,
2135 low_water_mark
, send_deltas
));
2137 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2138 _("Server doesn't support the replay "
2141 SVN_ERR(svn_ra_svn_drive_editor2(sess
->conn
, pool
, editor
, edit_baton
,
2144 SVN_ERR(svn_ra_svn_read_cmd_response(sess
->conn
, pool
, ""));
2146 return SVN_NO_ERROR
;
2150 static svn_error_t
*
2151 ra_svn_replay_range(svn_ra_session_t
*session
,
2152 svn_revnum_t start_revision
,
2153 svn_revnum_t end_revision
,
2154 svn_revnum_t low_water_mark
,
2155 svn_boolean_t send_deltas
,
2156 svn_ra_replay_revstart_callback_t revstart_func
,
2157 svn_ra_replay_revfinish_callback_t revfinish_func
,
2161 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2162 apr_pool_t
*iterpool
;
2165 SVN_ERR(svn_ra_svn_write_cmd(sess
->conn
, pool
, "replay-range", "rrrb",
2166 start_revision
, end_revision
,
2167 low_water_mark
, send_deltas
));
2169 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2170 _("Server doesn't support the replay-range "
2173 iterpool
= svn_pool_create(pool
);
2174 for (rev
= start_revision
; rev
<= end_revision
; rev
++)
2176 const svn_delta_editor_t
*editor
;
2178 apr_hash_t
*rev_props
;
2179 svn_ra_svn_item_t
*item
;
2181 svn_pool_clear(iterpool
);
2183 SVN_ERR(svn_ra_svn_read_item(sess
->conn
, iterpool
, &item
));
2184 if (item
->kind
!= SVN_RA_SVN_LIST
)
2185 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2186 _("Revision properties not a list"));
2188 SVN_ERR(svn_ra_svn_parse_proplist(item
->u
.list
, iterpool
, &rev_props
));
2190 SVN_ERR(revstart_func(rev
, replay_baton
,
2191 &editor
, &edit_baton
,
2194 SVN_ERR(svn_ra_svn_drive_editor2(sess
->conn
, iterpool
,
2197 SVN_ERR(revfinish_func(rev
, replay_baton
,
2202 svn_pool_destroy(iterpool
);
2204 SVN_ERR(svn_ra_svn_read_cmd_response(sess
->conn
, pool
, ""));
2206 return SVN_NO_ERROR
;
2210 static svn_error_t
*ra_svn_has_capability(svn_ra_session_t
*session
,
2212 const char *capability
,
2215 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2219 if (strcmp(capability
, SVN_RA_CAPABILITY_DEPTH
) == 0)
2220 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_DEPTH
);
2221 else if (strcmp(capability
, SVN_RA_CAPABILITY_MERGEINFO
) == 0)
2222 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_MERGEINFO
);
2223 else if (strcmp(capability
, SVN_RA_CAPABILITY_LOG_REVPROPS
) == 0)
2224 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_LOG_REVPROPS
);
2225 else if (strcmp(capability
, SVN_RA_CAPABILITY_PARTIAL_REPLAY
) == 0)
2226 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_PARTIAL_REPLAY
);
2227 else /* Don't know any other capabilities, so error. */
2229 return svn_error_createf
2230 (SVN_ERR_RA_UNKNOWN_CAPABILITY
, NULL
,
2231 _("Don't know anything about capability '%s'"), capability
);
2234 return SVN_NO_ERROR
;
2238 static const svn_ra__vtable_t ra_svn_vtable
= {
2240 ra_svn_get_description
,
2244 ra_svn_get_session_url
,
2245 ra_svn_get_latest_rev
,
2246 ra_svn_get_dated_rev
,
2247 ra_svn_change_rev_prop
,
2248 ra_svn_rev_proplist
,
2253 ra_svn_get_mergeinfo
,
2262 ra_svn_get_repos_root
,
2263 ra_svn_get_locations
,
2264 ra_svn_get_location_segments
,
2265 ra_svn_get_file_revs
,
2271 ra_svn_has_capability
,
2272 ra_svn_replay_range
,
2276 svn_ra_svn__init(const svn_version_t
*loader_version
,
2277 const svn_ra__vtable_t
**vtable
,
2280 static const svn_version_checklist_t checklist
[] =
2282 { "svn_subr", svn_subr_version
},
2283 { "svn_delta", svn_delta_version
},
2287 SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist
));
2289 /* Simplified version check to make sure we can safely use the
2290 VTABLE parameter. The RA loader does a more exhaustive check. */
2291 if (loader_version
->major
!= SVN_VER_MAJOR
)
2293 return svn_error_createf
2294 (SVN_ERR_VERSION_MISMATCH
, NULL
,
2295 _("Unsupported RA loader version (%d) for ra_svn"),
2296 loader_version
->major
);
2299 *vtable
= &ra_svn_vtable
;
2301 #ifdef SVN_HAVE_SASL
2302 SVN_ERR(svn_ra_svn__sasl_init());
2305 return SVN_NO_ERROR
;
2308 /* Compatibility wrapper for the 1.1 and before API. */
2309 #define NAME "ra_svn"
2310 #define DESCRIPTION RA_SVN_DESCRIPTION
2311 #define VTBL ra_svn_vtable
2312 #define INITFUNC svn_ra_svn__init
2313 #define COMPAT_INITFUNC svn_ra_svn_init
2314 #include "../libsvn_ra/wrapper_template.h"