blob: cb42080fe11c8d1e4d5a9c202c163cdb48409609 [file] [log] [blame]
/*
* options.c : entry point for OPTIONS RA functions for ra_serf
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <apr_uri.h>
#include <serf.h>
#include "svn_dirent_uri.h"
#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_ra.h"
#include "svn_dav.h"
#include "svn_xml.h"
#include "svn_ctype.h"
#include "../libsvn_ra/ra_loader.h"
#include "svn_private_config.h"
#include "private/svn_fspath.h"
#include "ra_serf.h"
/* In a debug build, setting this environment variable to "yes" will force
the client to speak v1, even if the server is capable of speaking v2. */
#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
/*
* This enum represents the current state of our XML parsing for an OPTIONS.
*/
enum options_state_e {
INITIAL = XML_STATE_INITIAL,
OPTIONS,
ACTIVITY_COLLECTION,
HREF
};
typedef struct options_context_t {
/* pool to allocate memory from */
apr_pool_t *pool;
/* Have we extracted options values from the headers already? */
svn_boolean_t headers_processed;
svn_ra_serf__session_t *session;
svn_ra_serf__handler_t *handler;
svn_ra_serf__response_handler_t inner_handler;
void *inner_baton;
const char *activity_collection;
svn_revnum_t youngest_rev;
} options_context_t;
#define D_ "DAV:"
#define S_ SVN_XML_NAMESPACE
static const svn_ra_serf__xml_transition_t options_ttable[] = {
{ INITIAL, D_, "options-response", OPTIONS,
FALSE, { NULL }, FALSE },
{ OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
FALSE, { NULL }, FALSE },
{ ACTIVITY_COLLECTION, D_, "href", HREF,
TRUE, { NULL }, TRUE },
{ 0 }
};
/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
options_closed(svn_ra_serf__xml_estate_t *xes,
void *baton,
int leaving_state,
const svn_string_t *cdata,
apr_hash_t *attrs,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx = baton;
SVN_ERR_ASSERT(leaving_state == HREF);
SVN_ERR_ASSERT(cdata != NULL);
opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
opt_ctx->pool);
return SVN_NO_ERROR;
}
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
create_options_body(serf_bucket_t **body_bkt,
void *baton,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool /* request pool */,
apr_pool_t *scratch_pool)
{
serf_bucket_t *body;
body = serf_bucket_aggregate_create(alloc);
svn_ra_serf__add_xml_header_buckets(body, alloc);
svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
"xmlns:D", "DAV:",
SVN_VA_NULL);
svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
*body_bkt = body;
return SVN_NO_ERROR;
}
/* We use these static pointers so we can employ pointer comparison
* of our capabilities hash members instead of strcmp()ing all over
* the place.
*/
/* Both server and repository support the capability. */
static const char *const capability_yes = "yes";
/* Either server or repository does not support the capability. */
static const char *const capability_no = "no";
/* Server supports the capability, but don't yet know if repository does. */
static const char *const capability_server_yes = "server-yes";
/* This implements serf_bucket_headers_do_callback_fn_t.
*/
static int
capabilities_headers_iterator_callback(void *baton,
const char *key,
const char *val)
{
options_context_t *opt_ctx = baton;
svn_ra_serf__session_t *session = opt_ctx->session;
if (svn_cstring_casecmp(key, "dav") == 0)
{
/* Each header may contain multiple values, separated by commas, e.g.:
DAV: version-control,checkout,working-resource
DAV: merge,baseline,activity,version-controlled-collection
DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
opt_ctx->pool);
/* Right now we only have a few capabilities to detect, so just
seek for them directly. This could be written slightly more
efficiently, but that wouldn't be worth it until we have many
more capabilities. */
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_DEPTH, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
{
/* The server doesn't know what repository we're referring
to, so it can't just say capability_yes. */
if (!svn_hash_gets(session->capabilities,
SVN_RA_CAPABILITY_MERGEINFO))
{
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_server_yes);
}
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
{
session->supports_inline_props = TRUE;
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
{
session->supports_rev_rsrc_replay = TRUE;
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
{
/* Use compressed svndiff1 format for servers that properly
advertise this capability (Subversion 1.10 and greater). */
session->supports_svndiff1 = TRUE;
}
}
/* SVN-specific headers -- if present, server supports HTTP protocol v2 */
else if (!svn_ctype_casecmp(key[0], 'S')
&& !svn_ctype_casecmp(key[1], 'V')
&& !svn_ctype_casecmp(key[2], 'N'))
{
/* If we've not yet seen any information about supported POST
requests, we'll initialize the list/hash with "create-txn"
(which we know is supported by virtue of the server speaking
HTTPv2 at all. */
if (! session->supported_posts)
{
session->supported_posts = apr_hash_make(session->pool);
apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
}
/* Use compressed svndiff1 format for servers that speak HTTPv2,
in addition to servers that send SVN_DAV_NS_DAV_SVN_SVNDIFF1.
Apache HTTPd + mod_dav_svn servers support svndiff1, beginning
from Subversion 1.4, but they do not advertise this capability.
Compressing data can have a noticeable impact if the connection
is slow, and we want to use it even for existing servers, so we
send svndiff1 data to every HTTPv2 server (Subversion 1.7 and
greater).
The reasoning behind enabling it with HTTPv2 is that if the user
is stuck with the old Subversion's HTTPv1 protocol, she probably
doesn't really care about performance. */
session->supports_svndiff1 = TRUE;
if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
{
session->repos_root = session->session_url;
session->repos_root.path =
(char *)svn_fspath__canonicalize(val, session->pool);
session->repos_root_str =
svn_urlpath__canonicalize(
apr_uri_unparse(session->pool, &session->repos_root, 0),
session->pool);
}
else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
{
#ifdef SVN_DEBUG
char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
if (!(ignore_v2_env_var
&& apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
session->me_resource = apr_pstrdup(session->pool, val);
#else
session->me_resource = apr_pstrdup(session->pool, val);
#endif
}
else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
{
session->rev_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
{
session->rev_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
{
session->txn_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
{
session->txn_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
{
session->vtxn_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
{
session->vtxn_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
{
session->uuid = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
{
opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
{
session->server_allows_bulk = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
{
/* May contain multiple values, separated by commas. */
int i;
apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
session->pool);
for (i = 0; i < vals->nelts; i++)
{
const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
svn_hash_sets(session->supported_posts, post_val, (void *)1);
}
}
else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
{
if (svn_cstring_casecmp(val, "yes") == 0)
{
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_yes);
}
else if (svn_cstring_casecmp(val, "no") == 0)
{
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_no);
}
}
}
return 0;
}
/* A custom serf_response_handler_t which is mostly a wrapper around
the expat-based response handler -- it just notices OPTIONS response
headers first, before handing off to the xml parser.
Implements svn_ra_serf__response_handler_t */
static svn_error_t *
options_response_handler(serf_request_t *request,
serf_bucket_t *response,
void *baton,
apr_pool_t *pool)
{
options_context_t *opt_ctx = baton;
if (!opt_ctx->headers_processed)
{
svn_ra_serf__session_t *session = opt_ctx->session;
serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
/* Start out assuming all capabilities are unsupported. */
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
NULL);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
capability_no);
/* Then see which ones we can discover. */
serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
opt_ctx);
/* Assume mergeinfo capability unsupported, if didn't receive information
about server or repository mergeinfo capability. */
if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_no);
opt_ctx->headers_processed = TRUE;
}
/* Execute the 'real' response handler to XML-parse the response body. */
return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
}
static svn_error_t *
create_options_req(options_context_t **opt_ctx,
svn_ra_serf__session_t *session,
apr_pool_t *pool)
{
options_context_t *new_ctx;
svn_ra_serf__xml_context_t *xmlctx;
svn_ra_serf__handler_t *handler;
new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
new_ctx->pool = pool;
new_ctx->session = session;
new_ctx->youngest_rev = SVN_INVALID_REVNUM;
xmlctx = svn_ra_serf__xml_context_create(options_ttable,
NULL, options_closed, NULL,
new_ctx,
pool);
handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
handler->method = "OPTIONS";
handler->path = session->session_url.path;
handler->body_delegate = create_options_body;
handler->body_type = "text/xml";
new_ctx->handler = handler;
new_ctx->inner_handler = handler->response_handler;
new_ctx->inner_baton = handler->response_baton;
handler->response_handler = options_response_handler;
handler->response_baton = new_ctx;
*opt_ctx = new_ctx;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
svn_ra_serf__session_t *session,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx;
SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
if (opt_ctx->handler->sline.code != 200)
return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
_("The OPTIONS response did not include "
"the youngest revision"));
*youngest = opt_ctx->youngest_rev;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__v1_get_activity_collection(const char **activity_url,
svn_ra_serf__session_t *session,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx;
SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
if (session->activity_collection_url)
{
*activity_url = apr_pstrdup(result_pool,
session->activity_collection_url);
return SVN_NO_ERROR;
}
SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
if (opt_ctx->handler->sline.code != 200)
return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
/* Cache the result. */
if (opt_ctx->activity_collection)
{
session->activity_collection_url =
apr_pstrdup(session->pool, opt_ctx->activity_collection);
}
else
{
return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
_("The OPTIONS response did not include the "
"requested activity-collection-set value"));
}
*activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
return SVN_NO_ERROR;
}
/** Capabilities exchange. */
svn_error_t *
svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
const char **corrected_url,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx;
if (corrected_url)
*corrected_url = NULL;
/* This routine automatically fills in serf_sess->capabilities */
SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
/* If our caller cares about server redirections, and our response
carries such a thing, report as much. We'll disregard ERR --
it's most likely just a complaint about the response body not
successfully parsing as XML or somesuch. */
if (corrected_url && (opt_ctx->handler->sline.code == 301))
{
if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
{
return svn_error_create(
SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
_("Location header not set on redirect response"));
}
else if (svn_path_is_url(opt_ctx->handler->location))
{
*corrected_url = svn_uri_canonicalize(opt_ctx->handler->location,
result_pool);
}
else
{
/* RFC1945 and RFC2616 state that the Location header's value
(from whence this CORRECTED_URL comes), if present, must be an
absolute URI. But some Apache versions (those older than 2.2.11,
it seems) transmit only the path portion of the URI.
See issue #3775 for details. */
apr_uri_t corrected_URI = serf_sess->session_url;
corrected_URI.path = (char *)corrected_url;
*corrected_url = svn_uri_canonicalize(
apr_uri_unparse(scratch_pool, &corrected_URI, 0),
result_pool);
}
return SVN_NO_ERROR;
}
else if (opt_ctx->handler->sline.code >= 300
&& opt_ctx->handler->sline.code < 399)
{
return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
(opt_ctx->handler->sline.code == 301
? _("Repository moved permanently to '%s'")
: _("Repository moved temporarily to '%s'")),
opt_ctx->handler->location);
}
if (opt_ctx->handler->sline.code != 200)
return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
/* Opportunistically cache any reported activity URL. (We don't
want to have to ask for this again later, potentially against an
unreadable commit anchor URL.) */
if (opt_ctx->activity_collection)
{
serf_sess->activity_collection_url =
apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
}
return SVN_NO_ERROR;
}
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
create_simple_options_body(serf_bucket_t **body_bkt,
void *baton,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool /* request pool */,
apr_pool_t *scratch_pool)
{
serf_bucket_t *body;
serf_bucket_t *s;
body = serf_bucket_aggregate_create(alloc);
svn_ra_serf__add_xml_header_buckets(body, alloc);
s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
serf_bucket_aggregate_append(body, s);
*body_bkt = body;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
apr_pool_t *scratch_pool)
{
svn_ra_serf__handler_t *handler;
handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
handler->method = "OPTIONS";
handler->path = serf_sess->session_url.path;
/* We don't care about the response body, so discard it. */
handler->response_handler = svn_ra_serf__handle_discard_body;
/* We need a simple body, in order to send it in chunked format. */
handler->body_delegate = create_simple_options_body;
handler->no_fail_on_http_failure_status = TRUE;
/* No special headers. */
SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
/* Some versions of nginx in reverse proxy mode will return 411. They want
a Content-Length header, rather than chunked requests. We can keep other
HTTP/1.1 features, but will disable the chunking. */
if (handler->sline.code == 411)
{
serf_sess->using_chunked_requests = FALSE;
return SVN_NO_ERROR;
}
if (handler->sline.code != 200)
SVN_ERR(svn_ra_serf__unexpected_status(handler));
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
svn_boolean_t *has,
const char *capability,
apr_pool_t *pool)
{
svn_ra_serf__session_t *serf_sess = ra_session->priv;
const char *cap_result;
/* This capability doesn't rely on anything server side. */
if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
{
*has = TRUE;
return SVN_NO_ERROR;
}
cap_result = svn_hash_gets(serf_sess->capabilities, capability);
/* If any capability is unknown, they're all unknown, so ask. */
if (cap_result == NULL)
SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool));
/* Try again, now that we've fetched the capabilities. */
cap_result = svn_hash_gets(serf_sess->capabilities, capability);
/* Some capabilities depend on the repository as well as the server. */
if (cap_result == capability_server_yes)
{
if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
{
/* Handle mergeinfo specially. Mergeinfo depends on the
repository as well as the server, but the server routine
that answered our svn_ra_serf__exchange_capabilities() call above
didn't even know which repository we were interested in
-- it just told us whether the server supports mergeinfo.
If the answer was 'no', there's no point checking the
particular repository; but if it was 'yes', we still must
change it to 'no' iff the repository itself doesn't
support mergeinfo. */
svn_mergeinfo_catalog_t ignored;
svn_error_t *err;
apr_array_header_t *paths = apr_array_make(pool, 1,
sizeof(char *));
APR_ARRAY_PUSH(paths, const char *) = "";
err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
svn_mergeinfo_explicit,
FALSE /* include_descendants */,
pool);
if (err)
{
if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
{
svn_error_clear(err);
cap_result = capability_no;
}
else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
{
/* Mergeinfo requests use relative paths, and
anyway we're in r0, so this is a likely error,
but it means the repository supports mergeinfo! */
svn_error_clear(err);
cap_result = capability_yes;
}
else
return svn_error_trace(err);
}
else
cap_result = capability_yes;
svn_hash_sets(serf_sess->capabilities,
SVN_RA_CAPABILITY_MERGEINFO, cap_result);
}
else
{
return svn_error_createf
(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
_("Don't know how to handle '%s' for capability '%s'"),
capability_server_yes, capability);
}
}
if (cap_result == capability_yes)
{
*has = TRUE;
}
else if (cap_result == capability_no)
{
*has = FALSE;
}
else if (cap_result == NULL)
{
return svn_error_createf
(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
_("Don't know anything about capability '%s'"), capability);
}
else /* "can't happen" */
{
/* Well, let's hope it's a string. */
return svn_error_createf
(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
_("Attempt to fetch capability '%s' resulted in '%s'"),
capability, cap_result);
}
return SVN_NO_ERROR;
}