2 * client.c : Functions for repository access via the Subversion protocol
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include "svn_private_config.h"
23 #define APR_WANT_STRFUNC
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
,
1055 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1056 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1058 apr_array_header_t
*mergeinfo_tuple
;
1059 svn_ra_svn_item_t
*elt
;
1060 const char *path
, *to_parse
;
1061 apr_hash_t
*for_path
;
1063 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "get-mergeinfo"));
1064 for (i
= 0; i
< paths
->nelts
; i
++)
1066 path
= APR_ARRAY_IDX(paths
, i
, const char *);
1067 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, path
));
1069 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)(?r)w)", revision
,
1070 svn_inheritance_to_word(inherit
)));
1072 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1073 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &mergeinfo_tuple
));
1076 if (mergeinfo_tuple
!= NULL
&& mergeinfo_tuple
->nelts
> 0)
1078 *mergeinfo
= apr_hash_make(pool
);
1079 for (i
= 0; i
< mergeinfo_tuple
->nelts
; i
++)
1081 elt
= &((svn_ra_svn_item_t
*) mergeinfo_tuple
->elts
)[i
];
1082 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1083 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1084 _("Mergeinfo element is not a list"));
1085 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, pool
, "cc",
1087 SVN_ERR(svn_mergeinfo_parse(&for_path
, to_parse
, pool
));
1088 apr_hash_set(*mergeinfo
, path
, APR_HASH_KEY_STRING
, for_path
);
1092 return SVN_NO_ERROR
;
1095 static svn_error_t
*ra_svn_update(svn_ra_session_t
*session
,
1096 const svn_ra_reporter3_t
**reporter
,
1097 void **report_baton
, svn_revnum_t rev
,
1098 const char *target
, svn_depth_t depth
,
1099 svn_boolean_t send_copyfrom_args
,
1100 const svn_delta_editor_t
*update_editor
,
1101 void *update_baton
, apr_pool_t
*pool
)
1103 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1104 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1105 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1107 /* Tell the server we want to start an update. */
1108 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "update", "(?r)cbwb", rev
, target
,
1109 recurse
, svn_depth_to_word(depth
),
1110 send_copyfrom_args
));
1111 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1113 /* Fetch a reporter for the caller to drive. The reporter will drive
1114 * update_editor upon finish_report(). */
1115 ra_svn_get_reporter(sess_baton
, pool
, update_editor
, update_baton
,
1116 target
, depth
, reporter
, report_baton
);
1117 return SVN_NO_ERROR
;
1120 static svn_error_t
*ra_svn_switch(svn_ra_session_t
*session
,
1121 const svn_ra_reporter3_t
**reporter
,
1122 void **report_baton
, svn_revnum_t rev
,
1123 const char *target
, svn_depth_t depth
,
1124 const char *switch_url
,
1125 const svn_delta_editor_t
*update_editor
,
1126 void *update_baton
, apr_pool_t
*pool
)
1128 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1129 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1130 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1132 /* Tell the server we want to start a switch. */
1133 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "switch", "(?r)cbcw", rev
,
1134 target
, recurse
, switch_url
,
1135 svn_depth_to_word(depth
)));
1136 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1138 /* Fetch a reporter for the caller to drive. The reporter will drive
1139 * update_editor upon finish_report(). */
1140 ra_svn_get_reporter(sess_baton
, pool
, update_editor
, update_baton
,
1141 target
, depth
, reporter
, report_baton
);
1142 return SVN_NO_ERROR
;
1145 static svn_error_t
*ra_svn_status(svn_ra_session_t
*session
,
1146 const svn_ra_reporter3_t
**reporter
,
1147 void **report_baton
,
1148 const char *target
, svn_revnum_t rev
,
1150 const svn_delta_editor_t
*status_editor
,
1151 void *status_baton
, apr_pool_t
*pool
)
1153 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1154 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1155 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1157 /* Tell the server we want to start a status operation. */
1158 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "status", "cb(?r)w",
1159 target
, recurse
, rev
,
1160 svn_depth_to_word(depth
)));
1161 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1163 /* Fetch a reporter for the caller to drive. The reporter will drive
1164 * status_editor upon finish_report(). */
1165 ra_svn_get_reporter(sess_baton
, pool
, status_editor
, status_baton
,
1166 target
, depth
, reporter
, report_baton
);
1167 return SVN_NO_ERROR
;
1170 static svn_error_t
*ra_svn_diff(svn_ra_session_t
*session
,
1171 const svn_ra_reporter3_t
**reporter
,
1172 void **report_baton
,
1173 svn_revnum_t rev
, const char *target
,
1175 svn_boolean_t ignore_ancestry
,
1176 svn_boolean_t text_deltas
,
1177 const char *versus_url
,
1178 const svn_delta_editor_t
*diff_editor
,
1179 void *diff_baton
, apr_pool_t
*pool
)
1181 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1182 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1183 svn_boolean_t recurse
= DEPTH_TO_RECURSE(depth
);
1185 /* Tell the server we want to start a diff. */
1186 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "diff", "(?r)cbbcbw", rev
,
1187 target
, recurse
, ignore_ancestry
,
1188 versus_url
, text_deltas
,
1189 svn_depth_to_word(depth
)));
1190 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1192 /* Fetch a reporter for the caller to drive. The reporter will drive
1193 * diff_editor upon finish_report(). */
1194 ra_svn_get_reporter(sess_baton
, pool
, diff_editor
, diff_baton
,
1195 target
, depth
, reporter
, report_baton
);
1196 return SVN_NO_ERROR
;
1199 static svn_error_t
*ra_svn_log(svn_ra_session_t
*session
,
1200 const apr_array_header_t
*paths
,
1201 svn_revnum_t start
, svn_revnum_t end
,
1203 svn_boolean_t discover_changed_paths
,
1204 svn_boolean_t strict_node_history
,
1205 svn_boolean_t include_merged_revisions
,
1206 apr_array_header_t
*revprops
,
1207 svn_log_entry_receiver_t receiver
,
1208 void *receiver_baton
, apr_pool_t
*pool
)
1210 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1211 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1212 apr_pool_t
*subpool
;
1214 const char *path
, *cpath
, *action
, *copy_path
;
1215 svn_string_t
*author
, *date
, *message
;
1216 svn_ra_svn_item_t
*item
, *elt
;
1218 apr_array_header_t
*cplist
, *rplist
;
1220 svn_revnum_t rev
, copy_rev
;
1221 svn_log_changed_path_t
*change
;
1223 apr_uint64_t has_children_param
, invalid_revnum_param
;
1224 svn_boolean_t has_children
;
1225 svn_log_entry_t
*log_entry
;
1226 svn_boolean_t want_custom_revprops
;
1227 apr_uint64_t revprop_count
;
1229 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((!", "log"));
1232 for (i
= 0; i
< paths
->nelts
; i
++)
1234 path
= APR_ARRAY_IDX(paths
, i
, const char *);
1235 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, path
));
1238 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!)(?r)(?r)bbnb!", start
, end
,
1239 discover_changed_paths
, strict_node_history
,
1240 (apr_uint64_t
) limit
,
1241 include_merged_revisions
));
1244 want_custom_revprops
= FALSE
;
1245 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!w(!", "revprops"));
1246 for (i
= 0; i
< revprops
->nelts
; i
++)
1248 name
= APR_ARRAY_IDX(revprops
, i
, char *);
1249 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, name
));
1250 if (!want_custom_revprops
1251 && strcmp(name
, SVN_PROP_REVISION_AUTHOR
) != 0
1252 && strcmp(name
, SVN_PROP_REVISION_DATE
) != 0
1253 && strcmp(name
, SVN_PROP_REVISION_LOG
) != 0)
1254 want_custom_revprops
= TRUE
;
1256 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1260 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!w())", "all-revprops"));
1261 want_custom_revprops
= TRUE
;
1264 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1266 /* Read the log messages. */
1267 subpool
= svn_pool_create(pool
);
1270 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &item
));
1271 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1273 if (item
->kind
!= SVN_RA_SVN_LIST
)
1274 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1275 _("Log entry not a list"));
1276 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, subpool
,
1277 "lr(?s)(?s)(?s)?BBnl",
1278 &cplist
, &rev
, &author
, &date
,
1279 &message
, &has_children_param
,
1280 &invalid_revnum_param
,
1281 &revprop_count
, &rplist
));
1282 if (want_custom_revprops
&& rplist
== NULL
)
1284 /* Caller asked for custom revprops, but server is too old. */
1285 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
,
1286 _("Server does not support custom revprops"
1290 if (has_children_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
1291 has_children
= FALSE
;
1293 has_children
= (svn_boolean_t
) has_children_param
;
1295 /* Because the svn protocol won't let us send an invalid revnum, we have
1296 to recover that fact using the extra parameter. */
1297 if (invalid_revnum_param
!= SVN_RA_SVN_UNSPECIFIED_NUMBER
1298 && invalid_revnum_param
== TRUE
)
1299 rev
= SVN_INVALID_REVNUM
;
1301 if (cplist
->nelts
> 0)
1303 /* Interpret the changed-paths list. */
1304 cphash
= apr_hash_make(subpool
);
1305 for (i
= 0; i
< cplist
->nelts
; i
++)
1307 elt
= &APR_ARRAY_IDX(cplist
, i
, svn_ra_svn_item_t
);
1308 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1309 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1310 _("Changed-path entry not a list"));
1311 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, subpool
, "cw(?cr)",
1312 &cpath
, &action
, ©_path
,
1314 cpath
= svn_path_canonicalize(cpath
, subpool
);
1316 copy_path
= svn_path_canonicalize(copy_path
, subpool
);
1317 change
= apr_palloc(subpool
, sizeof(*change
));
1318 change
->action
= *action
;
1319 change
->copyfrom_path
= copy_path
;
1320 change
->copyfrom_rev
= copy_rev
;
1321 apr_hash_set(cphash
, cpath
, APR_HASH_KEY_STRING
, change
);
1327 if (! (limit
&& ++nreceived
> limit
))
1329 log_entry
= svn_log_entry_create(subpool
);
1331 log_entry
->changed_paths
= cphash
;
1332 log_entry
->revision
= rev
;
1333 log_entry
->has_children
= has_children
;
1335 SVN_ERR(svn_ra_svn_parse_proplist(rplist
, pool
,
1336 &log_entry
->revprops
));
1337 if (log_entry
->revprops
== NULL
)
1338 log_entry
->revprops
= apr_hash_make(pool
);
1339 if (revprops
== NULL
)
1341 /* Caller requested all revprops; set author/date/log. */
1343 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_AUTHOR
,
1344 APR_HASH_KEY_STRING
, author
);
1346 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_DATE
,
1347 APR_HASH_KEY_STRING
, date
);
1349 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_LOG
,
1350 APR_HASH_KEY_STRING
, message
);
1354 /* Caller requested some; maybe set author/date/log. */
1355 for (i
= 0; i
< revprops
->nelts
; i
++)
1357 name
= APR_ARRAY_IDX(revprops
, i
, char *);
1358 if (author
&& strcmp(name
, SVN_PROP_REVISION_AUTHOR
) == 0)
1359 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_AUTHOR
,
1360 APR_HASH_KEY_STRING
, author
);
1361 if (date
&& strcmp(name
, SVN_PROP_REVISION_DATE
) == 0)
1362 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_DATE
,
1363 APR_HASH_KEY_STRING
, date
);
1364 if (message
&& strcmp(name
, SVN_PROP_REVISION_LOG
) == 0)
1365 apr_hash_set(log_entry
->revprops
, SVN_PROP_REVISION_LOG
,
1366 APR_HASH_KEY_STRING
, message
);
1369 SVN_ERR(receiver(receiver_baton
, log_entry
, subpool
));
1371 svn_pool_clear(subpool
);
1373 svn_pool_destroy(subpool
);
1375 /* Read the response. */
1376 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1378 return SVN_NO_ERROR
;
1382 static svn_error_t
*ra_svn_check_path(svn_ra_session_t
*session
,
1383 const char *path
, svn_revnum_t rev
,
1384 svn_node_kind_t
*kind
, apr_pool_t
*pool
)
1386 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1387 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1388 const char *kind_word
;
1390 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "check-path", "c(?r)", path
, rev
));
1391 SVN_ERR(handle_auth_request(sess_baton
, pool
));
1392 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "w", &kind_word
));
1393 SVN_ERR(interpret_kind(kind_word
, pool
, kind
));
1394 return SVN_NO_ERROR
;
1398 /* If ERR is a command not supported error, wrap it in a
1399 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
1400 static svn_error_t
*handle_unsupported_cmd(svn_error_t
*err
,
1403 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1404 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, err
,
1410 static svn_error_t
*ra_svn_stat(svn_ra_session_t
*session
,
1411 const char *path
, svn_revnum_t rev
,
1412 svn_dirent_t
**dirent
, apr_pool_t
*pool
)
1414 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1415 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1416 apr_array_header_t
*list
= NULL
;
1417 const char *kind
, *cdate
, *cauthor
;
1419 svn_boolean_t has_props
;
1421 svn_dirent_t
*the_dirent
;
1423 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "stat", "c(?r)", path
, rev
));
1425 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1426 _("'stat' not implemented")));
1428 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &list
));
1436 SVN_ERR(svn_ra_svn_parse_tuple(list
, pool
, "wnbr(?c)(?c)",
1437 &kind
, &size
, &has_props
,
1438 &crev
, &cdate
, &cauthor
));
1440 the_dirent
= apr_palloc(pool
, sizeof(*the_dirent
));
1441 SVN_ERR(interpret_kind(kind
, pool
, &the_dirent
->kind
));
1442 the_dirent
->size
= size
;/* FIXME: svn_filesize_t */
1443 the_dirent
->has_props
= has_props
;
1444 the_dirent
->created_rev
= crev
;
1445 SVN_ERR(svn_time_from_cstring(&the_dirent
->time
, cdate
, pool
));
1446 the_dirent
->last_author
= cauthor
;
1448 *dirent
= the_dirent
;
1451 return SVN_NO_ERROR
;
1455 static svn_error_t
*ra_svn_get_locations(svn_ra_session_t
*session
,
1456 apr_hash_t
**locations
,
1458 svn_revnum_t peg_revision
,
1459 apr_array_header_t
*location_revisions
,
1462 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1463 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1464 svn_revnum_t revision
;
1465 svn_ra_svn_item_t
*item
;
1466 svn_boolean_t is_done
;
1468 const char *ret_path
;
1470 /* Transmit the parameters. */
1471 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(cr(!",
1472 "get-locations", path
, peg_revision
));
1473 for (i
= 0; i
< location_revisions
->nelts
; i
++)
1475 revision
= APR_ARRAY_IDX(location_revisions
, i
, svn_revnum_t
);
1476 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!r!", revision
));
1479 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1481 /* Servers before 1.1 don't support this command. Check for this here. */
1482 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1483 _("'get-locations' not implemented")));
1485 /* Read the hash items. */
1487 *locations
= apr_hash_make(pool
);
1490 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &item
));
1491 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1493 else if (item
->kind
!= SVN_RA_SVN_LIST
)
1494 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1495 _("Location entry not a list"));
1498 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, pool
, "rc",
1499 &revision
, &ret_path
));
1500 ret_path
= svn_path_canonicalize(ret_path
, pool
);
1501 apr_hash_set(*locations
, apr_pmemdup(pool
, &revision
,
1503 sizeof(revision
), ret_path
);
1507 /* Read the response. This is so the server would have a chance to
1508 * report an error. */
1509 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1511 return SVN_NO_ERROR
;
1514 static svn_error_t
*
1515 ra_svn_get_location_segments(svn_ra_session_t
*session
,
1517 svn_revnum_t peg_revision
,
1518 svn_revnum_t start_rev
,
1519 svn_revnum_t end_rev
,
1520 svn_location_segment_receiver_t receiver
,
1521 void *receiver_baton
,
1524 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1525 svn_ra_svn_conn_t
*conn
= sess_baton
->conn
;
1526 svn_ra_svn_item_t
*item
;
1527 svn_boolean_t is_done
;
1528 svn_revnum_t range_start
, range_end
;
1529 const char *ret_path
;
1530 svn_location_segment_t
*segment
;
1531 apr_pool_t
*subpool
= svn_pool_create(pool
);
1533 /* Transmit the parameters. */
1534 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c(?r)(?r)(?r))",
1535 "get-location-segments",
1536 path
, peg_revision
, start_rev
, end_rev
));
1538 /* Servers before 1.1 don't support this command. Check for this here. */
1539 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1540 _("'get-location-segments' not implemented")));
1542 /* Parse the response. */
1546 svn_pool_clear(subpool
);
1547 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &item
));
1548 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1550 else if (item
->kind
!= SVN_RA_SVN_LIST
)
1551 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1552 _("Location segment entry not a list"));
1555 segment
= apr_pcalloc(subpool
, sizeof(*segment
));
1556 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, subpool
, "rr(?c)",
1557 &range_start
, &range_end
, &ret_path
));
1558 if (! (SVN_IS_VALID_REVNUM(range_start
)
1559 && SVN_IS_VALID_REVNUM(range_end
)))
1560 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1561 _("Expected valid revision range"));
1563 ret_path
= svn_path_canonicalize(ret_path
, subpool
);
1564 segment
->path
= ret_path
;
1565 segment
->range_start
= range_start
;
1566 segment
->range_end
= range_end
;
1567 SVN_ERR(receiver(segment
, receiver_baton
, subpool
));
1570 svn_pool_destroy(subpool
);
1572 /* Read the response. This is so the server would have a chance to
1573 * report an error. */
1574 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1576 return SVN_NO_ERROR
;
1579 static svn_error_t
*ra_svn_get_file_revs(svn_ra_session_t
*session
,
1581 svn_revnum_t start
, svn_revnum_t end
,
1582 svn_boolean_t include_merged_revisions
,
1583 svn_file_rev_handler_t handler
,
1584 void *handler_baton
, apr_pool_t
*pool
)
1586 svn_ra_svn__session_baton_t
*sess_baton
= session
->priv
;
1587 apr_pool_t
*rev_pool
, *chunk_pool
;
1588 svn_ra_svn_item_t
*item
;
1591 apr_array_header_t
*rev_proplist
, *proplist
;
1592 apr_hash_t
*rev_props
;
1593 apr_array_header_t
*props
;
1594 svn_boolean_t has_txdelta
;
1595 svn_boolean_t had_revision
= FALSE
;
1596 svn_stream_t
*stream
;
1597 svn_txdelta_window_handler_t d_handler
;
1600 apr_uint64_t merged_rev_param
;
1601 svn_boolean_t merged_rev
;
1603 /* One sub-pool for each revision and one for each txdelta chunk.
1604 Note that the rev_pool must live during the following txdelta. */
1605 rev_pool
= svn_pool_create(pool
);
1606 chunk_pool
= svn_pool_create(pool
);
1608 SVN_ERR(svn_ra_svn_write_cmd(sess_baton
->conn
, pool
, "get-file-revs",
1609 "c(?r)(?r)b", path
, start
, end
,
1610 include_merged_revisions
));
1612 /* Servers before 1.1 don't support this command. Check for this here. */
1613 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton
, pool
),
1614 _("'get-file-revs' not implemented")));
1618 svn_pool_clear(rev_pool
);
1619 svn_pool_clear(chunk_pool
);
1620 SVN_ERR(svn_ra_svn_read_item(sess_baton
->conn
, rev_pool
, &item
));
1621 if (item
->kind
== SVN_RA_SVN_WORD
&& strcmp(item
->u
.word
, "done") == 0)
1623 /* Either we've got a correct revision or we will error out below. */
1624 had_revision
= TRUE
;
1625 if (item
->kind
!= SVN_RA_SVN_LIST
)
1626 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1627 _("Revision entry not a list"));
1629 SVN_ERR(svn_ra_svn_parse_tuple(item
->u
.list
, rev_pool
,
1630 "crll?B", &p
, &rev
, &rev_proplist
,
1631 &proplist
, &merged_rev_param
));
1632 p
= svn_path_canonicalize(p
, rev_pool
);
1633 SVN_ERR(svn_ra_svn_parse_proplist(rev_proplist
, rev_pool
, &rev_props
));
1634 SVN_ERR(parse_prop_diffs(proplist
, rev_pool
, &props
));
1635 if (merged_rev_param
== SVN_RA_SVN_UNSPECIFIED_NUMBER
)
1638 merged_rev
= (svn_boolean_t
) merged_rev_param
;
1640 /* Get the first delta chunk so we know if there is a delta. */
1641 SVN_ERR(svn_ra_svn_read_item(sess_baton
->conn
, chunk_pool
, &item
));
1642 if (item
->kind
!= SVN_RA_SVN_STRING
)
1643 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1644 _("Text delta chunk not a string"));
1645 has_txdelta
= item
->u
.string
->len
> 0;
1647 SVN_ERR(handler(handler_baton
, p
, rev
, rev_props
, merged_rev
,
1648 has_txdelta
? &d_handler
: NULL
, &d_baton
,
1651 /* Process the text delta if any. */
1655 stream
= svn_txdelta_parse_svndiff(d_handler
, d_baton
, TRUE
,
1659 while (item
->u
.string
->len
> 0)
1661 size
= item
->u
.string
->len
;
1663 SVN_ERR(svn_stream_write(stream
, item
->u
.string
->data
, &size
));
1664 svn_pool_clear(chunk_pool
);
1666 SVN_ERR(svn_ra_svn_read_item(sess_baton
->conn
, chunk_pool
, &item
));
1667 if (item
->kind
!= SVN_RA_SVN_STRING
)
1668 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1669 _("Text delta chunk not a string"));
1672 SVN_ERR(svn_stream_close(stream
));
1676 SVN_ERR(svn_ra_svn_read_cmd_response(sess_baton
->conn
, pool
, ""));
1678 /* Return error if we didn't get any revisions. */
1680 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1681 _("The get-file-revs command didn't return "
1684 svn_pool_destroy(chunk_pool
);
1685 svn_pool_destroy(rev_pool
);
1687 return SVN_NO_ERROR
;
1690 /* For each path in PATH_REVS, send a 'lock' command to the server.
1691 Used with 1.2.x series servers which support locking, but of only
1692 one path at a time. ra_svn_lock(), which supports 'lock-many'
1693 is now the default. See svn_ra_lock() docstring for interface details. */
1694 static svn_error_t
*ra_svn_lock_compat(svn_ra_session_t
*session
,
1695 apr_hash_t
*path_revs
,
1696 const char *comment
,
1697 svn_boolean_t steal_lock
,
1698 svn_ra_lock_callback_t lock_func
,
1702 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1703 svn_ra_svn_conn_t
* conn
= sess
->conn
;
1704 apr_array_header_t
*list
;
1705 apr_hash_index_t
*hi
;
1706 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1708 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1714 svn_revnum_t
*revnum
;
1715 svn_error_t
*err
, *callback_err
= NULL
;
1717 svn_pool_clear(iterpool
);
1719 apr_hash_this(hi
, &key
, NULL
, &val
);
1723 SVN_ERR(svn_ra_svn_write_cmd(conn
, iterpool
, "lock", "c(?c)b(?r)",
1725 steal_lock
, *revnum
));
1727 /* Servers before 1.2 doesn't support locking. Check this here. */
1728 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
1729 _("Server doesn't support "
1730 "the lock command")));
1732 err
= svn_ra_svn_read_cmd_response(conn
, iterpool
, "l", &list
);
1735 SVN_ERR(parse_lock(list
, iterpool
, &lock
));
1737 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
1741 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
1744 svn_error_clear(err
);
1747 return callback_err
;
1750 svn_pool_destroy(iterpool
);
1752 return SVN_NO_ERROR
;
1755 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
1756 Used with 1.2.x series servers which support unlocking, but of only
1757 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
1758 now the default. See svn_ra_unlock() docstring for interface details. */
1759 static svn_error_t
*ra_svn_unlock_compat(svn_ra_session_t
*session
,
1760 apr_hash_t
*path_tokens
,
1761 svn_boolean_t break_lock
,
1762 svn_ra_lock_callback_t lock_func
,
1766 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1767 svn_ra_svn_conn_t
* conn
= sess
->conn
;
1768 apr_hash_index_t
*hi
;
1769 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1771 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1777 svn_error_t
*err
, *callback_err
= NULL
;
1779 svn_pool_clear(iterpool
);
1781 apr_hash_this(hi
, &key
, NULL
, &val
);
1783 if (strcmp(val
, "") != 0)
1788 SVN_ERR(svn_ra_svn_write_cmd(conn
, iterpool
, "unlock", "c(?c)b",
1789 path
, token
, break_lock
));
1791 /* Servers before 1.2 don't support locking. Check this here. */
1792 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, iterpool
),
1793 _("Server doesn't support the unlock "
1796 err
= svn_ra_svn_read_cmd_response(conn
, iterpool
, "");
1798 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
1802 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, pool
);
1804 svn_error_clear(err
);
1807 return callback_err
;
1810 svn_pool_destroy(iterpool
);
1812 return SVN_NO_ERROR
;
1815 /* Tell the server to lock all paths in PATH_REVS.
1816 See svn_ra_lock() for interface details. */
1817 static svn_error_t
*ra_svn_lock(svn_ra_session_t
*session
,
1818 apr_hash_t
*path_revs
,
1819 const char *comment
,
1820 svn_boolean_t steal_lock
,
1821 svn_ra_lock_callback_t lock_func
,
1825 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1826 svn_ra_svn_conn_t
*conn
= sess
->conn
;
1827 apr_hash_index_t
*hi
;
1828 svn_ra_svn_item_t
*elt
;
1829 svn_error_t
*err
, *callback_err
= SVN_NO_ERROR
;
1830 apr_pool_t
*subpool
= svn_pool_create(pool
);
1833 apr_array_header_t
*list
= NULL
;
1835 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w((?c)b(!", "lock-many",
1836 comment
, steal_lock
));
1838 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1843 svn_revnum_t
*revnum
;
1845 svn_pool_clear(subpool
);
1846 apr_hash_this(hi
, &key
, NULL
, &val
);
1850 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "c(?r)", path
, *revnum
));
1853 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1855 err
= handle_auth_request(sess
, pool
);
1857 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
1859 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1861 svn_error_clear(err
);
1862 return ra_svn_lock_compat(session
, path_revs
, comment
, steal_lock
,
1863 lock_func
, lock_baton
, pool
);
1869 /* Loop over responses to get lock information. */
1870 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1875 apr_hash_this(hi
, &key
, NULL
, NULL
);
1878 svn_pool_clear(subpool
);
1879 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &elt
));
1881 /* The server might have encountered some sort of fatal error in
1882 the middle of the request list. If this happens, it will
1883 transmit "done" to end the lock-info early, and then the
1884 overall command response will talk about the fatal error. */
1885 if (elt
->kind
== SVN_RA_SVN_WORD
&& strcmp(elt
->u
.word
, "done") == 0)
1888 if (elt
->kind
!= SVN_RA_SVN_LIST
)
1889 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1890 _("Lock response not a list"));
1892 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, subpool
, "wl", &status
,
1895 if (strcmp(status
, "failure") == 0)
1896 err
= svn_ra_svn__handle_failure_status(list
, subpool
);
1897 else if (strcmp(status
, "success") == 0)
1899 SVN_ERR(parse_lock(list
, subpool
, &lock
));
1903 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1904 _("Unknown status for lock command"));
1907 callback_err
= lock_func(lock_baton
, path
, TRUE
,
1911 callback_err
= SVN_NO_ERROR
;
1913 svn_error_clear(err
);
1916 return callback_err
;
1919 /* If we didn't break early above, and the whole hash was traversed,
1920 read the final "done" from the server. */
1923 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &elt
));
1924 if (elt
->kind
!= SVN_RA_SVN_WORD
|| strcmp(elt
->u
.word
, "done") != 0)
1925 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1926 _("Didn't receive end marker for lock "
1930 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
1932 svn_pool_destroy(subpool
);
1934 return SVN_NO_ERROR
;
1937 /* Tell the server to unlock all paths in PATH_TOKENS.
1938 See svn_ra_unlock() for interface details. */
1939 static svn_error_t
*ra_svn_unlock(svn_ra_session_t
*session
,
1940 apr_hash_t
*path_tokens
,
1941 svn_boolean_t break_lock
,
1942 svn_ra_lock_callback_t lock_func
,
1946 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
1947 svn_ra_svn_conn_t
*conn
= sess
->conn
;
1948 apr_hash_index_t
*hi
;
1949 apr_pool_t
*subpool
= svn_pool_create(pool
);
1950 svn_error_t
*err
, *callback_err
= NULL
;
1951 svn_ra_svn_item_t
*elt
;
1952 const char *status
= NULL
;
1953 apr_array_header_t
*list
= NULL
;
1957 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(b(!", "unlock-many",
1960 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1965 svn_pool_clear(subpool
);
1966 apr_hash_this(hi
, &key
, NULL
, &val
);
1969 if (strcmp(val
, "") != 0)
1974 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "c(?c)", path
, token
));
1977 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "!))"));
1979 err
= handle_auth_request(sess
, pool
);
1981 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
1984 if (err
&& err
->apr_err
== SVN_ERR_RA_SVN_UNKNOWN_CMD
)
1986 svn_error_clear(err
);
1987 return ra_svn_unlock_compat(session
, path_tokens
, break_lock
, lock_func
,
1994 /* Loop over responses to unlock files. */
1995 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1997 svn_pool_clear(subpool
);
1999 SVN_ERR(svn_ra_svn_read_item(conn
, subpool
, &elt
));
2001 /* The server might have encountered some sort of fatal error in
2002 the middle of the request list. If this happens, it will
2003 transmit "done" to end the lock-info early, and then the
2004 overall command response will talk about the fatal error. */
2005 if (elt
->kind
== SVN_RA_SVN_WORD
&& (strcmp(elt
->u
.word
, "done") == 0))
2008 apr_hash_this(hi
, &key
, NULL
, NULL
);
2011 if (elt
->kind
!= SVN_RA_SVN_LIST
)
2012 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2013 _("Unlock response not a list"));
2015 SVN_ERR(svn_ra_svn_parse_tuple(elt
->u
.list
, subpool
, "wl", &status
,
2018 if (strcmp(status
, "failure") == 0)
2019 err
= svn_ra_svn__handle_failure_status(list
, subpool
);
2020 else if (strcmp(status
, "success") == 0)
2022 SVN_ERR(svn_ra_svn_parse_tuple(list
, subpool
, "c", &path
));
2026 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2027 _("Unknown status for unlock command"));
2030 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
,
2033 callback_err
= SVN_NO_ERROR
;
2035 svn_error_clear(err
);
2038 return callback_err
;
2041 /* If we didn't break early above, and the whole hash was traversed,
2042 read the final "done" from the server. */
2045 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &elt
));
2046 if (elt
->kind
!= SVN_RA_SVN_WORD
|| strcmp(elt
->u
.word
, "done") != 0)
2047 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2048 _("Didn't receive end marker for unlock "
2052 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, ""));
2054 svn_pool_destroy(subpool
);
2056 return SVN_NO_ERROR
;
2059 static svn_error_t
*ra_svn_get_lock(svn_ra_session_t
*session
,
2064 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2065 svn_ra_svn_conn_t
* conn
= sess
->conn
;
2066 apr_array_header_t
*list
;
2068 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-lock", "c", path
));
2070 /* Servers before 1.2 doesn't support locking. Check this here. */
2071 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2072 _("Server doesn't support the get-lock "
2075 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "(?l)", &list
));
2077 SVN_ERR(parse_lock(list
, pool
, lock
));
2081 return SVN_NO_ERROR
;
2084 static svn_error_t
*ra_svn_get_locks(svn_ra_session_t
*session
,
2089 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2090 svn_ra_svn_conn_t
* conn
= sess
->conn
;
2091 apr_array_header_t
*list
;
2093 svn_ra_svn_item_t
*elt
;
2096 SVN_ERR(svn_ra_svn_write_cmd(conn
, pool
, "get-locks", "c", path
));
2098 /* Servers before 1.2 doesn't support locking. Check this here. */
2099 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2100 _("Server doesn't support the get-lock "
2103 SVN_ERR(svn_ra_svn_read_cmd_response(conn
, pool
, "l", &list
));
2105 *locks
= apr_hash_make(pool
);
2107 for (i
= 0; i
< list
->nelts
; ++i
)
2109 elt
= &APR_ARRAY_IDX(list
, i
, svn_ra_svn_item_t
);
2111 if (elt
->kind
!= SVN_RA_SVN_LIST
)
2112 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2113 _("Lock element not a list"));
2114 SVN_ERR(parse_lock(elt
->u
.list
, pool
, &lock
));
2115 apr_hash_set(*locks
, lock
->path
, APR_HASH_KEY_STRING
, lock
);
2118 return SVN_NO_ERROR
;
2122 static svn_error_t
*ra_svn_replay(svn_ra_session_t
*session
,
2123 svn_revnum_t revision
,
2124 svn_revnum_t low_water_mark
,
2125 svn_boolean_t send_deltas
,
2126 const svn_delta_editor_t
*editor
,
2130 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2132 SVN_ERR(svn_ra_svn_write_cmd(sess
->conn
, pool
, "replay", "rrb", revision
,
2133 low_water_mark
, send_deltas
));
2135 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2136 _("Server doesn't support the replay "
2139 SVN_ERR(svn_ra_svn_drive_editor2(sess
->conn
, pool
, editor
, edit_baton
,
2142 SVN_ERR(svn_ra_svn_read_cmd_response(sess
->conn
, pool
, ""));
2144 return SVN_NO_ERROR
;
2148 static svn_error_t
*
2149 ra_svn_replay_range(svn_ra_session_t
*session
,
2150 svn_revnum_t start_revision
,
2151 svn_revnum_t end_revision
,
2152 svn_revnum_t low_water_mark
,
2153 svn_boolean_t send_deltas
,
2154 svn_ra_replay_revstart_callback_t revstart_func
,
2155 svn_ra_replay_revfinish_callback_t revfinish_func
,
2159 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2160 apr_pool_t
*iterpool
;
2163 SVN_ERR(svn_ra_svn_write_cmd(sess
->conn
, pool
, "replay-range", "rrrb",
2164 start_revision
, end_revision
,
2165 low_water_mark
, send_deltas
));
2167 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess
, pool
),
2168 _("Server doesn't support the replay-range "
2171 iterpool
= svn_pool_create(pool
);
2172 for (rev
= start_revision
; rev
<= end_revision
; rev
++)
2174 const svn_delta_editor_t
*editor
;
2176 apr_hash_t
*rev_props
;
2177 svn_ra_svn_item_t
*item
;
2179 svn_pool_clear(iterpool
);
2181 SVN_ERR(svn_ra_svn_read_item(sess
->conn
, iterpool
, &item
));
2182 if (item
->kind
!= SVN_RA_SVN_LIST
)
2183 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
2184 _("Revision properties not a list"));
2186 SVN_ERR(svn_ra_svn_parse_proplist(item
->u
.list
, iterpool
, &rev_props
));
2188 SVN_ERR(revstart_func(rev
, replay_baton
,
2189 &editor
, &edit_baton
,
2192 SVN_ERR(svn_ra_svn_drive_editor2(sess
->conn
, iterpool
,
2195 SVN_ERR(revfinish_func(rev
, replay_baton
,
2200 svn_pool_destroy(iterpool
);
2202 SVN_ERR(svn_ra_svn_read_cmd_response(sess
->conn
, pool
, ""));
2204 return SVN_NO_ERROR
;
2208 static svn_error_t
*ra_svn_has_capability(svn_ra_session_t
*session
,
2210 const char *capability
,
2213 svn_ra_svn__session_baton_t
*sess
= session
->priv
;
2217 if (strcmp(capability
, SVN_RA_CAPABILITY_DEPTH
) == 0)
2218 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_DEPTH
);
2219 else if (strcmp(capability
, SVN_RA_CAPABILITY_MERGEINFO
) == 0)
2220 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_MERGEINFO
);
2221 else if (strcmp(capability
, SVN_RA_CAPABILITY_LOG_REVPROPS
) == 0)
2222 *has
= svn_ra_svn_has_capability(sess
->conn
, SVN_RA_SVN_CAP_LOG_REVPROPS
);
2223 else /* Don't know any other capabilities, so error. */
2225 return svn_error_createf
2226 (SVN_ERR_RA_UNKNOWN_CAPABILITY
, NULL
,
2227 _("Don't know anything about capability '%s'"), capability
);
2230 return SVN_NO_ERROR
;
2234 static const svn_ra__vtable_t ra_svn_vtable
= {
2236 ra_svn_get_description
,
2240 ra_svn_get_session_url
,
2241 ra_svn_get_latest_rev
,
2242 ra_svn_get_dated_rev
,
2243 ra_svn_change_rev_prop
,
2244 ra_svn_rev_proplist
,
2249 ra_svn_get_mergeinfo
,
2258 ra_svn_get_repos_root
,
2259 ra_svn_get_locations
,
2260 ra_svn_get_location_segments
,
2261 ra_svn_get_file_revs
,
2267 ra_svn_has_capability
,
2268 ra_svn_replay_range
,
2272 svn_ra_svn__init(const svn_version_t
*loader_version
,
2273 const svn_ra__vtable_t
**vtable
,
2276 static const svn_version_checklist_t checklist
[] =
2278 { "svn_subr", svn_subr_version
},
2279 { "svn_delta", svn_delta_version
},
2283 SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist
));
2285 /* Simplified version check to make sure we can safely use the
2286 VTABLE parameter. The RA loader does a more exhaustive check. */
2287 if (loader_version
->major
!= SVN_VER_MAJOR
)
2289 return svn_error_createf
2290 (SVN_ERR_VERSION_MISMATCH
, NULL
,
2291 _("Unsupported RA loader version (%d) for ra_svn"),
2292 loader_version
->major
);
2295 *vtable
= &ra_svn_vtable
;
2297 #ifdef SVN_HAVE_SASL
2298 SVN_ERR(svn_ra_svn__sasl_init());
2301 return SVN_NO_ERROR
;
2304 /* Compatibility wrapper for the 1.1 and before API. */
2305 #define NAME "ra_svn"
2306 #define DESCRIPTION RA_SVN_DESCRIPTION
2307 #define VTBL ra_svn_vtable
2308 #define INITFUNC svn_ra_svn__init
2309 #define COMPAT_INITFUNC svn_ra_svn_init
2310 #include "../libsvn_ra/wrapper_template.h"