2 * serf.c : entry point for ra_serf
4 * ====================================================================
5 * Copyright (c) 2006-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 #define APR_WANT_STRFUNC
30 #include "svn_pools.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_delta.h"
37 #include "svn_version.h"
41 #include "private/svn_dav_protocol.h"
42 #include "private/svn_dep_compat.h"
43 #include "svn_private_config.h"
48 /** Capabilities exchange. */
50 /* Both server and repository support the capability. */
51 static const char *capability_yes
= "yes";
52 /* Either server or repository does not support the capability. */
53 static const char *capability_no
= "no";
54 /* Server supports the capability, but don't yet know if repository does. */
55 static const char *capability_server_yes
= "server-yes";
57 /* Baton type for parsing capabilities out of "OPTIONS" response headers. */
58 struct capabilities_response_baton
60 /* This session's capabilities table. */
61 apr_hash_t
*capabilities
;
63 /* Signaler for svn_ra_serf__context_run_wait(). */
66 /* For temporary work only. */
71 /* This implements serf_bucket_headers_do_callback_fn_t.
72 * BATON is a 'struct capabilities_response_baton *'.
75 capabilities_headers_iterator_callback(void *baton
,
79 struct capabilities_response_baton
*crb
= baton
;
81 if (svn_cstring_casecmp(key
, "dav") == 0)
83 /* Each header may contain multiple values, separated by commas, e.g.:
84 DAV: version-control,checkout,working-resource
85 DAV: merge,baseline,activity,version-controlled-collection
86 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
87 apr_array_header_t
*vals
= svn_cstring_split(val
, ",", TRUE
, crb
->pool
);
89 /* Right now we only have a few capabilities to detect, so just
90 seek for them directly. This could be written slightly more
91 efficiently, but that wouldn't be worth it until we have many
94 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_DEPTH
, vals
))
96 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_DEPTH
,
97 APR_HASH_KEY_STRING
, capability_yes
);
100 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO
, vals
))
102 /* The server doesn't know what repository we're referring
103 to, so it can't just say capability_yes. */
104 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_MERGEINFO
,
105 APR_HASH_KEY_STRING
, capability_server_yes
);
108 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS
, vals
))
110 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_LOG_REVPROPS
,
111 APR_HASH_KEY_STRING
, capability_yes
);
114 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY
, vals
))
116 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_PARTIAL_REPLAY
,
117 APR_HASH_KEY_STRING
, capability_yes
);
125 /* This implements serf_response_handler_t.
126 * HANDLER_BATON is a 'struct capabilities_response_baton *'.
129 capabilities_response_handler(serf_request_t
*request
,
130 serf_bucket_t
*response
,
134 struct capabilities_response_baton
*crb
= handler_baton
;
135 serf_bucket_t
*hdrs
= serf_bucket_response_get_headers(response
);
137 /* Start out assuming all capabilities are unsupported. */
138 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_DEPTH
,
139 APR_HASH_KEY_STRING
, capability_no
);
140 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_MERGEINFO
,
141 APR_HASH_KEY_STRING
, capability_no
);
142 apr_hash_set(crb
->capabilities
, SVN_RA_CAPABILITY_LOG_REVPROPS
,
143 APR_HASH_KEY_STRING
, capability_no
);
145 /* Then see which ones we can discover. */
146 serf_bucket_headers_do(hdrs
, capabilities_headers_iterator_callback
, crb
);
148 /* Bunch of exit conditions to set before we go. */
150 serf_request_set_handler(request
, svn_ra_serf__handle_discard_body
, NULL
);
154 /* Set up headers announcing the client's capabilities.
155 BATON and POOL are ignored. */
157 set_up_capabilities_headers(serf_bucket_t
*headers
,
161 serf_bucket_headers_set(headers
, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH
);
162 serf_bucket_headers_set(headers
, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO
);
163 serf_bucket_headers_set(headers
, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS
);
169 /* Exchange capabilities with the server, by sending an OPTIONS
170 request announcing the client's capabilities, and by filling
171 SERF_SESS->capabilities with the server's capabilities as read
172 from the response headers. Use POOL only for temporary allocation. */
174 exchange_capabilities(svn_ra_serf__session_t
*serf_sess
, apr_pool_t
*pool
)
176 svn_ra_serf__handler_t
*handler
;
177 struct capabilities_response_baton crb
;
181 crb
.capabilities
= serf_sess
->capabilities
;
183 /* No obvious advantage to using svn_ra_serf__create_options_req() here. */
184 handler
= apr_pcalloc(pool
, sizeof(*handler
));
185 handler
->method
= "OPTIONS";
186 handler
->path
= serf_sess
->repos_url_str
;
187 handler
->body_buckets
= NULL
;
188 handler
->response_handler
= capabilities_response_handler
;
189 handler
->response_baton
= &crb
;
190 handler
->header_delegate
= set_up_capabilities_headers
;
191 handler
->session
= serf_sess
;
192 handler
->conn
= serf_sess
->conns
[0];
194 svn_ra_serf__request_create(handler
);
195 return svn_ra_serf__context_run_wait(&(crb
.done
), serf_sess
, pool
);
200 svn_ra_serf__has_capability(svn_ra_session_t
*ra_session
,
202 const char *capability
,
205 svn_ra_serf__session_t
*serf_sess
= ra_session
->priv
;
207 const char *cap_result
= apr_hash_get(serf_sess
->capabilities
,
209 APR_HASH_KEY_STRING
);
211 /* If any capability is unknown, they're all unknown, so ask. */
212 if (cap_result
== NULL
)
213 SVN_ERR(exchange_capabilities(serf_sess
, pool
));
215 /* Try again, now that we've fetched the capabilities. */
216 cap_result
= apr_hash_get(serf_sess
->capabilities
,
217 capability
, APR_HASH_KEY_STRING
);
219 /* Some capabilities depend on the repository as well as the server.
220 NOTE: ../libsvn_ra_neon/session.c:svn_ra_neon__has_capability()
221 has a very similar code block. If you change something here,
222 check there as well. */
223 if (cap_result
== capability_server_yes
)
225 if (strcmp(capability
, SVN_RA_CAPABILITY_MERGEINFO
) == 0)
227 /* Handle mergeinfo specially. Mergeinfo depends on the
228 repository as well as the server, but the server routine
229 that answered our exchange_capabilities() call above
230 didn't even know which repository we were interested in
231 -- it just told us whether the server supports mergeinfo.
232 If the answer was 'no', there's no point checking the
233 particular repository; but if it was 'yes, we still must
234 change it to 'no' iff the repository itself doesn't
235 support mergeinfo. */
236 svn_mergeinfo_catalog_t ignored
;
238 apr_array_header_t
*paths
= apr_array_make(pool
, 1,
240 APR_ARRAY_PUSH(paths
, const char *) = "";
242 err
= svn_ra_serf__get_mergeinfo(ra_session
, &ignored
, paths
, 0,
247 if (err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)
249 svn_error_clear(err
);
250 cap_result
= capability_no
;
252 else if (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
253 || err
->apr_err
== SVN_ERR_RA_DAV_PATH_NOT_FOUND
)
255 /* Mergeinfo requests use relative paths, and
256 anyway we're in r0, so this is a likely error,
257 but it means the repository supports mergeinfo! */
258 svn_error_clear(err
);
259 cap_result
= capability_yes
;
265 cap_result
= capability_yes
;
267 apr_hash_set(serf_sess
->capabilities
,
268 SVN_RA_CAPABILITY_MERGEINFO
, APR_HASH_KEY_STRING
,
273 return svn_error_createf
274 (SVN_ERR_UNKNOWN_CAPABILITY
, NULL
,
275 _("Don't know how to handle '%s' for capability '%s'"),
276 capability_server_yes
, capability
);
280 if (cap_result
== capability_yes
)
284 else if (cap_result
== capability_no
)
288 else if (cap_result
== NULL
)
290 return svn_error_createf
291 (SVN_ERR_UNKNOWN_CAPABILITY
, NULL
,
292 _("Don't know anything about capability '%s'"), capability
);
294 else /* "can't happen" */
296 /* Well, let's hope it's a string. */
297 return svn_error_createf
298 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
299 _("Attempt to fetch capability '%s' resulted in '%s'"),
300 capability
, cap_result
);
308 static const svn_version_t
*
309 ra_serf_version(void)
314 #define RA_SERF_DESCRIPTION \
315 N_("Module for accessing a repository via WebDAV protocol using serf.")
318 ra_serf_get_description(void)
320 return _(RA_SERF_DESCRIPTION
);
323 static const char * const *
324 ra_serf_get_schemes(apr_pool_t
*pool
)
326 static const char *serf_ssl
[] = { "http", "https", NULL
};
328 /* ### Temporary: to shut up a warning. */
329 static const char *serf_no_ssl
[] = { "http", NULL
};
332 /* TODO: Runtime detection. */
337 load_config(svn_ra_serf__session_t
*session
,
338 apr_hash_t
*config_hash
,
341 svn_config_t
*config
;
342 const char *server_group
;
343 #if SERF_VERSION_AT_LEAST(0, 1, 3)
344 const char *proxy_host
= NULL
, *port_str
= NULL
;
345 unsigned int proxy_port
;
348 config
= apr_hash_get(config_hash
, SVN_CONFIG_CATEGORY_SERVERS
,
349 APR_HASH_KEY_STRING
);
351 SVN_ERR(svn_config_get_bool(config
, &session
->using_compression
,
352 SVN_CONFIG_SECTION_GLOBAL
,
353 SVN_CONFIG_OPTION_HTTP_COMPRESSION
, TRUE
));
355 svn_auth_set_parameter(session
->wc_callbacks
->auth_baton
,
356 SVN_AUTH_PARAM_CONFIG
, config
);
358 #if SERF_VERSION_AT_LEAST(0, 1, 3)
359 /* Load the global proxy server settings, if set. */
360 svn_config_get(config
, &proxy_host
, SVN_CONFIG_SECTION_GLOBAL
,
361 SVN_CONFIG_OPTION_HTTP_PROXY_HOST
, NULL
);
362 svn_config_get(config
, &port_str
, SVN_CONFIG_SECTION_GLOBAL
,
363 SVN_CONFIG_OPTION_HTTP_PROXY_PORT
, NULL
);
364 svn_config_get(config
, &session
->proxy_username
, SVN_CONFIG_SECTION_GLOBAL
,
365 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME
, NULL
);
366 svn_config_get(config
, &session
->proxy_password
, SVN_CONFIG_SECTION_GLOBAL
,
367 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD
, NULL
);
370 server_group
= svn_config_find_group(config
,
371 session
->repos_url
.hostname
,
372 SVN_CONFIG_SECTION_GROUPS
, pool
);
376 SVN_ERR(svn_config_get_bool(config
, &session
->using_compression
,
378 SVN_CONFIG_OPTION_HTTP_COMPRESSION
,
379 session
->using_compression
));
380 svn_auth_set_parameter(session
->wc_callbacks
->auth_baton
,
381 SVN_AUTH_PARAM_SERVER_GROUP
, server_group
);
383 #if SERF_VERSION_AT_LEAST(0, 1, 3)
384 /* Load the group proxy server settings, overriding global settings. */
385 svn_config_get(config
, &proxy_host
, server_group
,
386 SVN_CONFIG_OPTION_HTTP_PROXY_HOST
, NULL
);
387 svn_config_get(config
, &port_str
, server_group
,
388 SVN_CONFIG_OPTION_HTTP_PROXY_PORT
, NULL
);
389 svn_config_get(config
, &session
->proxy_username
, server_group
,
390 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME
, NULL
);
391 svn_config_get(config
, &session
->proxy_password
, server_group
,
392 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD
, NULL
);
396 #if SERF_VERSION_AT_LEAST(0, 1, 3)
397 /* Convert the proxy port value, if any. */
401 const long int port
= strtol(port_str
, &endstr
, 10);
404 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
405 _("Invalid URL: illegal character in proxy "
408 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
409 _("Invalid URL: negative proxy port number"));
411 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
412 _("Invalid URL: proxy port number greater "
413 "than maximum TCP port number 65535"));
421 apr_sockaddr_t
*proxy_addr
;
424 status
= apr_sockaddr_info_get(&proxy_addr
, proxy_host
,
425 APR_INET
, proxy_port
, 0,
427 session
->using_proxy
= TRUE
;
428 serf_config_proxy(session
->context
, proxy_addr
);
431 session
->using_proxy
= FALSE
;
437 #if SERF_VERSION_AT_LEAST(0,1,3)
439 svn_ra_serf__progress(void *progress_baton
, apr_off_t read
, apr_off_t written
)
441 const svn_ra_serf__session_t
*serf_sess
= progress_baton
;
442 if (serf_sess
->wc_progress_func
)
444 serf_sess
->wc_progress_func(read
+ written
, -1,
445 serf_sess
->wc_progress_baton
,
452 svn_ra_serf__open(svn_ra_session_t
*session
,
453 const char *repos_URL
,
454 const svn_ra_callbacks2_t
*callbacks
,
455 void *callback_baton
,
460 svn_ra_serf__session_t
*serf_sess
;
462 const char *client_string
= NULL
;
464 serf_sess
= apr_pcalloc(pool
, sizeof(*serf_sess
));
465 apr_pool_create(&serf_sess
->pool
, pool
);
466 serf_sess
->bkt_alloc
= serf_bucket_allocator_create(serf_sess
->pool
, NULL
,
468 serf_sess
->cached_props
= apr_hash_make(serf_sess
->pool
);
469 serf_sess
->wc_callbacks
= callbacks
;
470 serf_sess
->wc_callback_baton
= callback_baton
;
471 serf_sess
->wc_progress_baton
= callbacks
->progress_baton
;
472 serf_sess
->wc_progress_func
= callbacks
->progress_func
;
474 /* todo: reuse serf context across sessions */
475 serf_sess
->context
= serf_context_create(serf_sess
->pool
);
477 status
= apr_uri_parse(serf_sess
->pool
, repos_URL
, &url
);
480 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
481 _("Illegal repository URL '%s'"),
485 serf_sess
->repos_url
= url
;
486 serf_sess
->repos_url_str
= apr_pstrdup(serf_sess
->pool
, repos_URL
);
490 url
.port
= apr_uri_port_of_scheme(url
.scheme
);
492 serf_sess
->using_ssl
= (svn_cstring_casecmp(url
.scheme
, "https") == 0);
494 serf_sess
->capabilities
= apr_hash_make(serf_sess
->pool
);
496 SVN_ERR(load_config(serf_sess
, config
, serf_sess
->pool
));
498 /* register cleanups */
499 apr_pool_cleanup_register(serf_sess
->pool
, serf_sess
,
500 svn_ra_serf__cleanup_serf_session
,
501 apr_pool_cleanup_null
);
503 serf_sess
->conns
= apr_palloc(serf_sess
->pool
, sizeof(*serf_sess
->conns
) * 4);
505 serf_sess
->conns
[0] = apr_pcalloc(serf_sess
->pool
,
506 sizeof(*serf_sess
->conns
[0]));
507 serf_sess
->conns
[0]->bkt_alloc
=
508 serf_bucket_allocator_create(serf_sess
->pool
, NULL
, NULL
);
509 serf_sess
->conns
[0]->session
= serf_sess
;
510 serf_sess
->conns
[0]->last_status_code
= -1;
512 /* Unless we're using a proxy, fetch the DNS record for this host */
513 if (! serf_sess
->using_proxy
)
515 status
= apr_sockaddr_info_get(&serf_sess
->conns
[0]->address
,
517 APR_UNSPEC
, url
.port
, 0, serf_sess
->pool
);
520 return svn_error_wrap_apr(status
,
521 _("Could not lookup hostname `%s'"),
527 /* Create an address with unresolved hostname. */
528 apr_sockaddr_t
*sa
= apr_pcalloc(serf_sess
->pool
, sizeof(apr_sockaddr_t
));
529 sa
->pool
= serf_sess
->pool
;
530 sa
->hostname
= apr_pstrdup(serf_sess
->pool
, url
.hostname
);
532 sa
->family
= APR_UNSPEC
;
533 serf_sess
->conns
[0]->address
= sa
;
536 serf_sess
->conns
[0]->using_ssl
= serf_sess
->using_ssl
;
537 serf_sess
->conns
[0]->using_compression
= serf_sess
->using_compression
;
538 serf_sess
->conns
[0]->hostinfo
= url
.hostinfo
;
539 serf_sess
->conns
[0]->auth_header
= NULL
;
540 serf_sess
->conns
[0]->auth_value
= NULL
;
541 serf_sess
->conns
[0]->useragent
= NULL
;
543 /* create the user agent string */
544 if (callbacks
->get_client_string
)
545 callbacks
->get_client_string(callback_baton
, &client_string
, pool
);
548 serf_sess
->conns
[0]->useragent
= apr_pstrcat(pool
, USER_AGENT
, "/",
549 client_string
, NULL
);
551 serf_sess
->conns
[0]->useragent
= USER_AGENT
;
553 /* go ahead and tell serf about the connection. */
554 serf_sess
->conns
[0]->conn
=
555 serf_connection_create(serf_sess
->context
, serf_sess
->conns
[0]->address
,
556 svn_ra_serf__conn_setup
, serf_sess
->conns
[0],
557 svn_ra_serf__conn_closed
, serf_sess
->conns
[0],
560 /* Set the progress callback. */
561 #if SERF_VERSION_AT_LEAST(0,1,3)
562 serf_context_set_progress_cb(serf_sess
->context
, svn_ra_serf__progress
,
566 serf_sess
->num_conns
= 1;
568 session
->priv
= serf_sess
;
570 SVN_ERR(exchange_capabilities(serf_sess
, pool
));
576 svn_ra_serf__reparent(svn_ra_session_t
*ra_session
,
580 svn_ra_serf__session_t
*session
= ra_session
->priv
;
584 /* If it's the URL we already have, wave our hands and do nothing. */
585 if (strcmp(session
->repos_url_str
, url
) == 0)
590 /* Do we need to check that it's the same host and port? */
591 status
= apr_uri_parse(session
->pool
, url
, &new_url
);
594 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
595 _("Illegal repository URL '%s'"), url
);
598 session
->repos_url
.path
= new_url
.path
;
599 session
->repos_url_str
= apr_pstrdup(session
->pool
, url
);
605 svn_ra_serf__get_session_url(svn_ra_session_t
*ra_session
,
609 svn_ra_serf__session_t
*session
= ra_session
->priv
;
610 *url
= apr_pstrdup(pool
, session
->repos_url_str
);
615 svn_ra_serf__get_latest_revnum(svn_ra_session_t
*ra_session
,
616 svn_revnum_t
*latest_revnum
,
620 svn_ra_serf__session_t
*session
= ra_session
->priv
;
621 const char *vcc_url
, *baseline_url
, *version_name
;
623 props
= apr_hash_make(pool
);
625 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, NULL
,
626 session
, session
->conns
[0],
627 session
->repos_url
.path
, pool
));
631 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
632 _("The OPTIONS response did not include the "
633 "requested version-controlled-configuration "
637 /* Using the version-controlled-configuration, fetch the checked-in prop. */
638 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
639 vcc_url
, SVN_INVALID_REVNUM
, "0",
640 checked_in_props
, pool
));
642 baseline_url
= svn_ra_serf__get_prop(props
, vcc_url
,
643 "DAV:", "checked-in");
647 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
648 _("The OPTIONS response did not include the "
649 "requested checked-in value"));
652 /* Using the checked-in property, fetch:
653 * baseline-connection *and* version-name
655 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
656 baseline_url
, SVN_INVALID_REVNUM
,
657 "0", baseline_props
, pool
));
659 version_name
= svn_ra_serf__get_prop(props
, baseline_url
,
660 "DAV:", SVN_DAV__VERSION_NAME
);
664 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
665 _("The OPTIONS response did not include the "
666 "requested version-name value"));
669 *latest_revnum
= SVN_STR_TO_REV(version_name
);
675 svn_ra_serf__rev_proplist(svn_ra_session_t
*ra_session
,
677 apr_hash_t
**ret_props
,
680 svn_ra_serf__session_t
*session
= ra_session
->priv
;
684 props
= apr_hash_make(pool
);
685 *ret_props
= apr_hash_make(pool
);
687 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, NULL
,
688 session
, session
->conns
[0],
689 session
->repos_url
.path
, pool
));
691 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
692 vcc_url
, rev
, "0", all_props
, pool
));
694 svn_ra_serf__walk_all_props(props
, vcc_url
, rev
, svn_ra_serf__set_bare_props
,
701 svn_ra_serf__rev_prop(svn_ra_session_t
*session
,
704 svn_string_t
**value
,
709 SVN_ERR(svn_ra_serf__rev_proplist(session
, rev
, &props
, pool
));
711 *value
= apr_hash_get(props
, name
, APR_HASH_KEY_STRING
);
717 fetch_path_props(svn_ra_serf__propfind_context_t
**ret_prop_ctx
,
718 apr_hash_t
**ret_props
,
719 const char **ret_path
,
720 svn_revnum_t
*ret_revision
,
721 svn_ra_serf__session_t
*session
,
722 const char *rel_path
,
723 svn_revnum_t revision
,
724 const svn_ra_serf__dav_props_t
*desired_props
,
727 svn_ra_serf__propfind_context_t
*prop_ctx
;
731 path
= session
->repos_url
.path
;
733 /* If we have a relative path, append it. */
736 path
= svn_path_url_add_component(path
, rel_path
, pool
);
739 props
= apr_hash_make(pool
);
743 /* If we were given a specific revision, we have to fetch the VCC and
744 * do a PROPFIND off of that.
746 if (!SVN_IS_VALID_REVNUM(revision
))
748 svn_ra_serf__deliver_props(&prop_ctx
, props
, session
, session
->conns
[0],
749 path
, revision
, "0", desired_props
, TRUE
,
750 NULL
, session
->pool
);
754 const char *vcc_url
, *relative_url
, *basecoll_url
;
756 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, &relative_url
,
757 session
, session
->conns
[0],
760 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
762 "0", baseline_props
, pool
));
764 basecoll_url
= svn_ra_serf__get_ver_prop(props
, vcc_url
, revision
,
765 "DAV:", "baseline-collection");
769 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
770 _("The OPTIONS response did not include the "
771 "requested baseline-collection value"));
774 /* We will try again with our new path; however, we're now
775 * technically an unversioned resource because we are accessing
776 * the revision's baseline-collection.
779 path
= svn_path_url_add_component(basecoll_url
, relative_url
, pool
);
780 revision
= SVN_INVALID_REVNUM
;
781 svn_ra_serf__deliver_props(&prop_ctx
, props
, session
, session
->conns
[0],
784 NULL
, session
->pool
);
789 SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx
, session
, pool
));
793 *ret_prop_ctx
= prop_ctx
;
795 *ret_revision
= revision
;
801 svn_ra_serf__check_path(svn_ra_session_t
*ra_session
,
802 const char *rel_path
,
803 svn_revnum_t revision
,
804 svn_node_kind_t
*kind
,
807 svn_ra_serf__session_t
*session
= ra_session
->priv
;
809 svn_ra_serf__propfind_context_t
*prop_ctx
;
810 const char *path
, *res_type
;
811 svn_revnum_t fetched_rev
;
813 SVN_ERR(fetch_path_props(&prop_ctx
, &props
, &path
, &fetched_rev
,
815 revision
, check_path_props
, pool
));
817 if (prop_ctx
&& (svn_ra_serf__propfind_status_code(prop_ctx
) == 404))
819 *kind
= svn_node_none
;
823 res_type
= svn_ra_serf__get_ver_prop(props
, path
, fetched_rev
,
824 "DAV:", "resourcetype");
827 /* How did this happen? */
828 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
829 _("The OPTIONS response did not include the "
830 "requested resourcetype value"));
832 else if (strcmp(res_type
, "collection") == 0)
834 *kind
= svn_node_dir
;
838 *kind
= svn_node_file
;
846 dirent_walker(void *baton
,
847 const char *ns
, apr_ssize_t ns_len
,
848 const char *name
, apr_ssize_t name_len
,
849 const svn_string_t
*val
,
852 svn_dirent_t
*entry
= baton
;
854 if (strcmp(ns
, SVN_DAV_PROP_NS_CUSTOM
) == 0)
856 entry
->has_props
= TRUE
;
858 else if (strcmp(ns
, SVN_DAV_PROP_NS_SVN
) == 0)
860 entry
->has_props
= TRUE
;
862 else if (strcmp(ns
, "DAV:") == 0)
864 if (strcmp(name
, SVN_DAV__VERSION_NAME
) == 0)
866 entry
->created_rev
= SVN_STR_TO_REV(val
->data
);
868 else if (strcmp(name
, "creator-displayname") == 0)
870 entry
->last_author
= val
->data
;
872 else if (strcmp(name
, SVN_DAV__CREATIONDATE
) == 0)
874 SVN_ERR(svn_time_from_cstring(&entry
->time
, val
->data
, pool
));
876 else if (strcmp(name
, "getcontentlength") == 0)
878 entry
->size
= apr_atoi64(val
->data
);
880 else if (strcmp(name
, "resourcetype") == 0)
882 if (strcmp(val
->data
, "collection") == 0)
884 entry
->kind
= svn_node_dir
;
888 entry
->kind
= svn_node_file
;
896 struct path_dirent_visitor_t
{
897 apr_hash_t
*full_paths
;
898 apr_hash_t
*base_paths
;
899 const char *orig_path
;
903 path_dirent_walker(void *baton
,
904 const char *path
, apr_ssize_t path_len
,
905 const char *ns
, apr_ssize_t ns_len
,
906 const char *name
, apr_ssize_t name_len
,
907 const svn_string_t
*val
,
910 struct path_dirent_visitor_t
*dirents
= baton
;
913 /* Skip our original path. */
914 if (strcmp(path
, dirents
->orig_path
) == 0)
919 entry
= apr_hash_get(dirents
->full_paths
, path
, path_len
);
923 const char *base_name
;
925 entry
= apr_pcalloc(pool
, sizeof(*entry
));
927 apr_hash_set(dirents
->full_paths
, path
, path_len
, entry
);
929 base_name
= svn_path_uri_decode(svn_path_basename(path
, pool
), pool
);
931 apr_hash_set(dirents
->base_paths
, base_name
, APR_HASH_KEY_STRING
, entry
);
934 return dirent_walker(entry
, ns
, ns_len
, name
, name_len
, val
, pool
);
938 svn_ra_serf__stat(svn_ra_session_t
*ra_session
,
939 const char *rel_path
,
940 svn_revnum_t revision
,
941 svn_dirent_t
**dirent
,
944 svn_ra_serf__session_t
*session
= ra_session
->priv
;
946 svn_ra_serf__propfind_context_t
*prop_ctx
;
948 svn_revnum_t fetched_rev
;
951 SVN_ERR(fetch_path_props(&prop_ctx
, &props
, &path
, &fetched_rev
,
952 session
, rel_path
, revision
, all_props
, pool
));
954 entry
= apr_pcalloc(pool
, sizeof(*entry
));
956 svn_ra_serf__walk_all_props(props
, path
, fetched_rev
, dirent_walker
, entry
,
965 svn_ra_serf__get_dir(svn_ra_session_t
*ra_session
,
966 apr_hash_t
**dirents
,
967 svn_revnum_t
*fetched_rev
,
968 apr_hash_t
**ret_props
,
969 const char *rel_path
,
970 svn_revnum_t revision
,
971 apr_uint32_t dirent_fields
,
974 svn_ra_serf__session_t
*session
= ra_session
->priv
;
978 path
= session
->repos_url
.path
;
980 /* If we have a relative path, append it. */
983 path
= svn_path_url_add_component(path
, rel_path
, pool
);
986 props
= apr_hash_make(pool
);
988 if (SVN_IS_VALID_REVNUM(revision
) || fetched_rev
)
990 const char *vcc_url
, *relative_url
, *basecoll_url
;
992 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, &relative_url
,
993 session
, session
->conns
[0],
996 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
998 "0", baseline_props
, pool
));
1000 basecoll_url
= svn_ra_serf__get_ver_prop(props
, vcc_url
, revision
,
1001 "DAV:", "baseline-collection");
1005 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED
, NULL
,
1006 _("The OPTIONS response did not include the "
1007 "requested baseline-collection value"));
1012 *fetched_rev
= revision
;
1015 path
= svn_path_url_add_component(basecoll_url
, relative_url
, pool
);
1016 revision
= SVN_INVALID_REVNUM
;
1019 /* If we're asked for children, fetch them now. */
1022 svn_ra_serf__propfind_context_t
*prop_ctx
;
1023 struct path_dirent_visitor_t dirent_walk
;
1026 svn_ra_serf__deliver_props(&prop_ctx
, props
, session
, session
->conns
[0],
1027 path
, revision
, "1", all_props
, TRUE
,
1028 NULL
, session
->pool
);
1032 SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx
, session
, pool
));
1035 /* We're going to create two hashes to help the walker along.
1036 * We're going to return the 2nd one back to the caller as it
1037 * will have the basenames it expects.
1039 dirent_walk
.full_paths
= apr_hash_make(pool
);
1040 dirent_walk
.base_paths
= apr_hash_make(pool
);
1041 dirent_walk
.orig_path
= svn_path_canonicalize(path
, pool
);
1043 svn_ra_serf__walk_all_paths(props
, revision
, path_dirent_walker
,
1044 &dirent_walk
, pool
);
1046 *dirents
= dirent_walk
.base_paths
;
1049 /* If we're asked for the directory properties, fetch them too. */
1052 props
= apr_hash_make(pool
);
1053 *ret_props
= apr_hash_make(pool
);
1055 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
1056 path
, revision
, "0", all_props
,
1058 svn_ra_serf__walk_all_props(props
, path
, revision
,
1059 svn_ra_serf__set_flat_props
,
1063 return SVN_NO_ERROR
;
1066 static svn_error_t
*
1067 svn_ra_serf__get_repos_root(svn_ra_session_t
*ra_session
,
1071 svn_ra_serf__session_t
*session
= ra_session
->priv
;
1073 if (!session
->repos_root_str
)
1075 const char *vcc_url
;
1077 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, NULL
,
1078 session
, session
->conns
[0],
1079 session
->repos_url
.path
, pool
));
1082 *url
= session
->repos_root_str
;
1083 return SVN_NO_ERROR
;
1086 static svn_error_t
*
1087 svn_ra_serf__get_uuid(svn_ra_session_t
*ra_session
,
1091 svn_ra_serf__session_t
*session
= ra_session
->priv
;
1093 const char *root_url
;
1095 props
= apr_hash_make(pool
);
1097 SVN_ERR(svn_ra_serf__get_repos_root(ra_session
, &root_url
, pool
));
1098 SVN_ERR(svn_ra_serf__retrieve_props(props
, session
, session
->conns
[0],
1099 root_url
, SVN_INVALID_REVNUM
, "0",
1101 *uuid
= svn_ra_serf__get_prop(props
, root_url
,
1102 SVN_DAV_PROP_NS_DAV
, "repository-uuid");
1106 return svn_error_create(APR_EGENERAL
, NULL
,
1107 _("The UUID property was not found on the "
1108 "resource or any of its parents"));
1111 return SVN_NO_ERROR
;
1115 static const svn_ra__vtable_t serf_vtable
= {
1117 ra_serf_get_description
,
1118 ra_serf_get_schemes
,
1120 svn_ra_serf__reparent
,
1121 svn_ra_serf__get_session_url
,
1122 svn_ra_serf__get_latest_revnum
,
1123 svn_ra_serf__get_dated_revision
,
1124 svn_ra_serf__change_rev_prop
,
1125 svn_ra_serf__rev_proplist
,
1126 svn_ra_serf__rev_prop
,
1127 svn_ra_serf__get_commit_editor
,
1128 svn_ra_serf__get_file
,
1129 svn_ra_serf__get_dir
,
1130 svn_ra_serf__get_mergeinfo
,
1131 svn_ra_serf__do_update
,
1132 svn_ra_serf__do_switch
,
1133 svn_ra_serf__do_status
,
1134 svn_ra_serf__do_diff
,
1135 svn_ra_serf__get_log
,
1136 svn_ra_serf__check_path
,
1138 svn_ra_serf__get_uuid
,
1139 svn_ra_serf__get_repos_root
,
1140 svn_ra_serf__get_locations
,
1141 svn_ra_serf__get_location_segments
,
1142 svn_ra_serf__get_file_revs
,
1144 svn_ra_serf__unlock
,
1145 svn_ra_serf__get_lock
,
1146 svn_ra_serf__get_locks
,
1147 svn_ra_serf__replay
,
1148 svn_ra_serf__has_capability
,
1149 svn_ra_serf__replay_range
,
1153 svn_ra_serf__init(const svn_version_t
*loader_version
,
1154 const svn_ra__vtable_t
**vtable
,
1157 static const svn_version_checklist_t checklist
[] =
1159 { "svn_subr", svn_subr_version
},
1160 { "svn_delta", svn_delta_version
},
1164 SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist
));
1166 /* Simplified version check to make sure we can safely use the
1167 VTABLE parameter. The RA loader does a more exhaustive check. */
1168 if (loader_version
->major
!= SVN_VER_MAJOR
)
1170 return svn_error_createf
1171 (SVN_ERR_VERSION_MISMATCH
, NULL
,
1172 _("Unsupported RA loader version (%d) for ra_serf"),
1173 loader_version
->major
);
1176 *vtable
= &serf_vtable
;
1178 return SVN_NO_ERROR
;
1181 /* Compatibility wrapper for pre-1.2 subversions. Needed? */
1182 #define NAME "ra_serf"
1183 #define DESCRIPTION RA_SERF_DESCRIPTION
1184 #define VTBL serf_vtable
1185 #define INITFUNC svn_ra_serf__init
1186 #define COMPAT_INITFUNC svn_ra_serf_init
1187 #include "../libsvn_ra/wrapper_template.h"