rendered paste body[[[
Fix issue #2959: depth upgrade against old servers was broken.
### PATCH IN PROGRESS, DO NOT APPLY ###
* subversion/include/svn_error_codes.h
(SVN_ERR_RA_UNKNOWN_CAPABILITY): New error code.
* subversion/include/svn_dav.h
(SVN_DAV_PROP_NS_DAV_SVN_DEPTH): New header for response to OPTIONS request.
* subversion/mod_dav_svn/version.c
(get_vsn_options): Add above new header to OPTIONS response.
* subversion/libsvn_ra/ra_loader.h
(svn_ra__vtable_t): Add new has_capability() prototype.
* subversion/include/svn_ra.h
(svn_ra_has_capability): New prototype.
(SVN_RA_CAPABILITY_DEPTH): New constant.
* subversion/libsvn_ra/ra_loader.c
(svn_ra_has_capability): New function definition.
* subversion/libsvn_ra_neon/ra_neon.h
(svn_ra_neon__has_capability): New prototype.
(svn_ra_neon__session_t): Add new 'capabilities' hash.
* subversion/libsvn_ra_neon/session.c
(capability_yes, capability_no): New static placeholder ints.
(discover_capabilities, parse_capabilities): New helper functions.
(svn_ra_neon__has_capability): New function.
(neon_vtable): Add svn_ra_neon__has_capability.
* subversion/libsvn_ra_serf/serf.c
(capabilities_headers_iterator_callback): New function.
(capabilities_response_handler): New function.
(svn_ra_serf__has_capability): New function.
(serf_vtable): Add svn_ra_serf__has_capability.
* subversion/include/svn_wc.h
(svn_wc_crawl_revisions3): Add new parameter depth_compatibility_trick.
* subversion/libsvn_wc/adm_crawler.c
(svn_wc_crawl_revisions3, report_revisions_and_depths): Take new
parameter depth_compatibility_trick. Conditionally send TRUE for
start_empty, depending on entry depths and on whether or not this
is a depth upgrade.
(svn_wc_crawl_revisions2): Adjust wrapper call.
* subversion/libsvn_client/update.c
(svn_client__update_internal): Detect depth capability in server,
use it to determine how to call svn_wc_crawl_revisions3.
* subversion/libsvn_client/switch.c
(svn_client__switch_internal): Same.
* subversion/libsvn_client/status.c
(svn_client_status3): Same.
* subversion/libsvn_client/diff.c
(diff_repos_wc): Same.
* subversion/include/svn_ra_svn.h
(SVN_RA_SVN_CAP_DEPTH): New capability.
* subversion/svnserve/serve.c
(serve): Send new capability.
* subversion/libsvn_ra_svn/client.c
(ra_svn_has_capability): New function.
(ra_svn_vtable): Add ra_svn_has_capability.
* subversion/libsvn_ra_local/ra_plugin.c
(svn_ra_local__has_capability): New function.
(ra_local_vtable): Add svn_ra_local__has_capability.
]]]
Index: subversion/libsvn_ra/ra_loader.c
===================================================================
--- subversion/libsvn_ra/ra_loader.c (revision 27149)
+++ subversion/libsvn_ra/ra_loader.c (working copy)
@@ -1047,6 +1047,19 @@
text_deltas, editor, edit_baton, pool);
}
+svn_error_t *svn_ra_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ /* fooo: all RA layers should implement this */
+ if (session->vtable->has_capability)
+ return session->vtable->has_capability(session, has, capability, pool);
+
+ *has = FALSE;
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
Index: subversion/libsvn_ra/ra_loader.h
===================================================================
--- subversion/libsvn_ra/ra_loader.h (revision 27149)
+++ subversion/libsvn_ra/ra_loader.h (working copy)
@@ -231,6 +231,10 @@
const svn_delta_editor_t *editor,
void *edit_baton,
apr_pool_t *pool);
+ svn_error_t *(*has_capability)(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
} svn_ra__vtable_t;
/* The RA session object. */
Index: subversion/include/svn_error_codes.h
===================================================================
--- subversion/include/svn_error_codes.h (revision 27149)
+++ subversion/include/svn_error_codes.h (working copy)
@@ -715,6 +715,10 @@
SVN_ERR_RA_CATEGORY_START + 7,
"Path is not locked")
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RA_UNKNOWN_CAPABILITY,
+ SVN_ERR_RA_CATEGORY_START + 8,
+ "Inquiry about unknown capability")
/* ra_dav errors */
Index: subversion/include/svn_dav.h
===================================================================
--- subversion/include/svn_dav.h (revision 27149)
+++ subversion/include/svn_dav.h (working copy)
@@ -149,6 +149,14 @@
*/
#define SVN_DAV_PROP_NS_DAV "http://subversion.tigris.org/xmlns/dav/"
+/* Presence of this in the DAV header response to an OPTIONS request
+ indicates that the server supports @c svn_depth_t.
+
+ ### TODO: this doesn't really have anything to do with properties,
+ ### but we should re-use the SVN_DAV_PROP_NS_DAV, right? We could
+ ### change the name of SVN_DAV_PROP_NS_DAV_SVN_DEPTH, though. */
+#define SVN_DAV_PROP_NS_DAV_SVN_DEPTH SVN_DAV_PROP_NS_DAV "svn/depth"
+
/** @} */
#ifdef __cplusplus
Index: subversion/include/svn_ra_svn.h
===================================================================
--- subversion/include/svn_ra_svn.h (revision 27149)
+++ subversion/include/svn_ra_svn.h (working copy)
@@ -45,6 +45,7 @@
#define SVN_RA_SVN_CAP_ABSENT_ENTRIES "absent-entries"
#define SVN_RA_SVN_CAP_COMMIT_REVPROPS "commit-revprops"
#define SVN_RA_SVN_CAP_MERGEINFO "mergeinfo"
+#define SVN_RA_SVN_CAP_DEPTH "depth" /* maps to SVN_RA_CAPABILITY_DEPTH */
/** ra_svn passes @c svn_dirent_t fields over the wire as a list of
* words, these are the values used to represent each field.
Index: subversion/include/svn_wc.h
===================================================================
--- subversion/include/svn_wc.h (revision 27149)
+++ subversion/include/svn_wc.h (working copy)
@@ -2962,6 +2962,12 @@
* course. If @a depth is @c svn_depth_unknown, then just use
* @c svn_depth_infinity, which in practice means depth of @a path.
*
+ * Iff @a depth_compatibility_trick is TRUE, then set the @c start_empty
+ * flag on @a reporter->set_path() and @a reporter->link_path() calls
+ * as necessary to trick a pre-1.5 (i.e., depth-unaware) server into
+ * sending back all the items the client might need to upgrade a
+ * working copy from a shallower depth to a deeper one.
+ *
* If @a restore_files is true, then unexpectedly missing working files
* will be restored from the administrative directory's cache. For each
* file restored, the @a notify_func function will be called with the
@@ -2983,6 +2989,7 @@
void *report_baton,
svn_boolean_t restore_files,
svn_depth_t depth,
+ svn_boolean_t depth_compatibility_trick,
svn_boolean_t use_commit_times,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h (revision 27149)
+++ subversion/include/svn_ra.h (working copy)
@@ -1511,6 +1511,36 @@
apr_pool_t *pool);
/**
+ * Set @a *has to true if the server represented by @a session has
+ * capability @a capability (one of the capabilities beginning with
+ * @c "SVN_RA_CAPABILITY_"), else set @a *has to false.
+ *
+ * Use @a pool for all allocation.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *svn_ra_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+/**
+ * The capability of understanding @c svn_depth_t (e.g., the server
+ * understands what the client means when the client describes the
+ * depth of a working copy to the server.)
+ *
+ * @since New in 1.5.
+ */
+#define SVN_RA_CAPABILITY_DEPTH "depth"
+
+/* *** PLEASE READ THIS IF YOU ADD A NEW CAPABILITY ***
+ * RA layers generally fetch all capabilities when asked about any
+ * capability, to save future round trips. So if you add a new
+ * capability here, make sure to update the RA layers to remember
+ * it after any capabilities query.
+ */
+
+/**
* Append a textual list of all available RA modules to the stringbuf
* @a output.
*
Index: subversion/libsvn_wc/adm_crawler.c
===================================================================
--- subversion/libsvn_wc/adm_crawler.c (revision 27149)
+++ subversion/libsvn_wc/adm_crawler.c (working copy)
@@ -157,6 +157,9 @@
comes back from the server will be different for svn_depth_unknown
than for svn_depth_infinity.)
+ DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
+ in svn_wc_crawl_revisions3().
+
If TRAVERSAL_INFO is non-null, record this directory's
value of svn:externals in both TRAVERSAL_INFO->externals_old and
TRAVERSAL_INFO->externals_new, using wc_path + dir_path as the key,
@@ -182,6 +185,7 @@
void *notify_baton,
svn_boolean_t restore_files,
svn_depth_t depth,
+ svn_boolean_t depth_compatibility_trick,
svn_boolean_t report_everything,
svn_boolean_t use_commit_times,
svn_wc_traversal_info_t *traversal_info,
@@ -368,6 +372,7 @@
{
svn_wc_adm_access_t *subdir_access;
const svn_wc_entry_t *subdir_entry;
+ svn_boolean_t start_empty;
/* If a directory is missing from disk, we have no way to
recreate it locally, so report as missing and move
@@ -390,6 +395,14 @@
SVN_ERR(svn_wc_entry(&subdir_entry, this_full_path, subdir_access,
TRUE, iterpool));
+ start_empty = subdir_entry->incomplete;
+ if (depth_compatibility_trick
+ && subdir_entry->depth <= svn_depth_files
+ && depth > subdir_entry->depth)
+ {
+ start_empty = TRUE;
+ }
+
if (report_everything)
{
/* Report the dir unconditionally, one way or another. */
@@ -398,14 +411,14 @@
subdir_entry->url,
subdir_entry->revision,
subdir_entry->depth,
- subdir_entry->incomplete,
+ start_empty,
subdir_entry->lock_token,
iterpool));
else
SVN_ERR(reporter->set_path(report_baton, this_path,
subdir_entry->revision,
subdir_entry->depth,
- subdir_entry->incomplete,
+ start_empty,
subdir_entry->lock_token,
iterpool));
}
@@ -417,7 +430,7 @@
subdir_entry->url,
subdir_entry->revision,
subdir_entry->depth,
- subdir_entry->incomplete,
+ start_empty,
subdir_entry->lock_token,
iterpool));
/* ... or perhaps just a differing revision, lock token, incomplete
@@ -435,7 +448,7 @@
this_path,
subdir_entry->revision,
subdir_entry->depth,
- subdir_entry->incomplete,
+ start_empty,
subdir_entry->lock_token,
iterpool));
@@ -445,7 +458,8 @@
reporter, report_baton,
notify_func, notify_baton,
restore_files, depth,
- subdir_entry->incomplete,
+ depth_compatibility_trick,
+ start_empty,
use_commit_times,
traversal_info,
iterpool));
@@ -470,6 +484,7 @@
void *report_baton,
svn_boolean_t restore_files,
svn_depth_t depth,
+ svn_boolean_t depth_compatibility_trick,
svn_boolean_t use_commit_times,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
@@ -482,6 +497,7 @@
svn_boolean_t missing = FALSE;
const svn_wc_entry_t *parent_entry = NULL;
svn_wc_notify_t *notify;
+ svn_boolean_t start_empty;
/* The first thing we do is get the base_rev from the working copy's
ROOT_DIRECTORY. This is the first revnum that entries will be
@@ -518,6 +534,15 @@
}
base_rev = entry->revision;
+
+ start_empty = entry->incomplete;
+ if (depth_compatibility_trick
+ && entry->depth <= svn_depth_immediates
+ && depth > entry->depth)
+ {
+ start_empty = TRUE;
+ }
+
if (base_rev == SVN_INVALID_REVNUM)
{
const char *dirname = svn_path_dirname(path, pool);
@@ -530,8 +555,7 @@
top-level directory being updated is at BASE_REV. Its PATH
argument is ignored. */
SVN_ERR(reporter->set_path(report_baton, "", base_rev, entry->depth,
- entry->incomplete , /* start_empty ? */
- NULL, pool));
+ start_empty, NULL, pool));
if (entry->schedule != svn_wc_schedule_delete)
{
@@ -566,7 +590,8 @@
reporter, report_baton,
notify_func, notify_baton,
restore_files, depth,
- entry->incomplete,
+ depth_compatibility_trick,
+ start_empty,
use_commit_times,
traversal_info,
pool);
@@ -753,6 +778,7 @@
&wrap_3to2_reporter, &wrb,
restore_files,
SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ FALSE,
use_commit_times,
notify_func,
notify_baton,
Index: subversion/libsvn_ra_local/ra_plugin.c
===================================================================
--- subversion/libsvn_ra_local/ra_plugin.c (revision 27149)
+++ subversion/libsvn_ra_local/ra_plugin.c (working copy)
@@ -1415,6 +1415,26 @@
}
+static svn_error_t *
+svn_ra_local__has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0)
+ {
+ *has = TRUE;
+ }
+ else /* Don't know any other capabilities yet, so error. */
+ {
+ return svn_error_createf
+ (SVN_ERR_RA_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"), capability);
+ }
+
+ return SVN_NO_ERROR;
+}
+
/*----------------------------------------------------------------*/
static const svn_version_t *
@@ -1459,6 +1479,7 @@
svn_ra_local__get_lock,
svn_ra_local__get_locks,
svn_ra_local__replay,
+ svn_ra_local__has_capability
};
Index: subversion/libsvn_client/switch.c
===================================================================
--- subversion/libsvn_client/switch.c (revision 27149)
+++ subversion/libsvn_client/switch.c (working copy)
@@ -83,6 +83,7 @@
svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool);
const char *preserved_exts_str;
apr_array_header_t *preserved_exts;
+ svn_boolean_t server_supports_depth;
svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config,
SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING)
@@ -181,6 +182,9 @@
target, depth, switch_url,
switch_editor, switch_edit_baton, pool));
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
/* Drive the reporter structure, describing the revisions within
PATH. When we call reporter->finish_report, the update_editor
will be driven by svn_repos_dir_delta2.
@@ -189,7 +193,8 @@
update, and therefore we don't want to handle any externals
except the ones directly affected by the switch. */
err = svn_wc_crawl_revisions3(path, dir_access, reporter, report_baton,
- TRUE, depth, use_commit_times,
+ TRUE, depth, (! server_supports_depth),
+ use_commit_times,
ctx->notify_func2, ctx->notify_baton2,
NULL, /* no traversal info */
pool);
Index: subversion/libsvn_client/status.c
===================================================================
--- subversion/libsvn_client/status.c (revision 27149)
+++ subversion/libsvn_client/status.c (working copy)
@@ -277,6 +277,7 @@
svn_ra_session_t *ra_session;
const char *URL;
svn_node_kind_t kind;
+ svn_boolean_t server_supports_depth;
/* Get full URL from the ANCHOR. */
if (! entry)
@@ -343,14 +344,17 @@
rb.ctx = ctx;
rb.pool = pool;
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
/* Drive the reporter structure, describing the revisions
within PATH. When we call reporter->finish_report,
EDITOR will be driven to describe differences between our
working copy and HEAD. */
SVN_ERR(svn_wc_crawl_revisions3(path, target_access,
&lock_fetch_reporter, &rb, FALSE,
- depth, FALSE, NULL, NULL, NULL,
- pool));
+ depth, (! server_supports_depth),
+ FALSE, NULL, NULL, NULL, pool));
}
}
else
Index: subversion/libsvn_client/diff.c
===================================================================
--- subversion/libsvn_client/diff.c (revision 27149)
+++ subversion/libsvn_client/diff.c (working copy)
@@ -1206,6 +1206,7 @@
void *diff_edit_baton;
svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
int levels_to_lock = levels_to_lock_from_depth(depth);
+ svn_boolean_t server_supports_depth;
/* Assert that we have valid input. */
assert(! svn_path_is_url(path2));
@@ -1291,12 +1292,15 @@
url1,
diff_editor, diff_edit_baton, pool));
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
/* Create a txn mirror of path2; the diff editor will print
diffs in reverse. :-) */
SVN_ERR(svn_wc_crawl_revisions3(path2, dir_access,
reporter, report_baton,
- FALSE, depth, FALSE,
- NULL, NULL, /* notification is N/A */
+ FALSE, depth, (! server_supports_depth),
+ FALSE, NULL, NULL, /* notification is N/A */
NULL, pool));
SVN_ERR(svn_wc_adm_close(adm_access));
Index: subversion/libsvn_client/update.c
===================================================================
--- subversion/libsvn_client/update.c (revision 27149)
+++ subversion/libsvn_client/update.c (working copy)
@@ -113,6 +113,7 @@
const char *preserved_exts_str;
apr_array_header_t *preserved_exts;
struct ff_baton *ffb;
+ svn_boolean_t server_supports_depth;
svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config,
SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING) : NULL;
@@ -230,11 +231,15 @@
TRUE, /* send copyfrom args, please */
update_editor, update_edit_baton, pool));
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
/* Drive the reporter structure, describing the revisions within
PATH. When we call reporter->finish_report, the
update_editor will be driven by svn_repos_dir_delta2. */
err = svn_wc_crawl_revisions3(path, dir_access, reporter, report_baton,
- TRUE, depth, use_commit_times,
+ TRUE, depth, (! server_supports_depth),
+ use_commit_times,
ctx->notify_func2, ctx->notify_baton2,
traversal_info, pool);
Index: subversion/libsvn_ra_serf/serf.c
===================================================================
--- subversion/libsvn_ra_serf/serf.c (revision 27149)
+++ subversion/libsvn_ra_serf/serf.c (working copy)
@@ -732,6 +732,104 @@
return SVN_NO_ERROR;
}
+
+/* Baton type for parsing capabilities out of "OPTIONS" response headers. */
+struct capabilities_response_baton
+{
+ /* Signaler for svn_ra_serf__context_run_wait(). */
+ svn_boolean_t done;
+};
+
+
+/* This implements serf_bucket_headers_do_callback_fn_t.
+ * BATON is a 'struct capabilities_response_baton *'.
+ */
+static int
+capabilities_headers_iterator_callback(void *baton,
+ const char *key,
+ const char *value)
+{
+ printf("KFF key: '%s'\n", key);
+ printf("KFF val: '%s'\n", value);
+ fflush(stdout);
+ return 0;
+}
+
+
+/* This implements serf_response_handler_t.
+ * HANDLER_BATON is a 'struct capabilities_response_baton *'.
+ */
+static apr_status_t
+capabilities_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ struct capabilities_response_baton *crb = handler_baton;
+
+ printf("KFF: in capabilities_response_handler()\n");
+ fflush(stdout);
+ printf("KFF: about to get response headers\n");
+ fflush(stdout);
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+ printf("KFF: got response headers\n");
+ fflush(stdout);
+
+ printf("KFF: about to enter iteration\n");
+ fflush(stdout);
+ serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, NULL);
+ printf("KFF: done with iteration\n");
+ fflush(stdout);
+ crb->done = TRUE;
+
+ return APR_SUCCESS;
+}
+
+static 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;
+ svn_ra_serf__handler_t *handler;
+ struct capabilities_response_baton crb;
+
+ crb.done = FALSE;
+
+ printf("KFF: in serf, asking about '%s'\n", capability);
+ fflush(stdout);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->method = "OPTIONS";
+ handler->path = "";
+ handler->body_buckets = NULL;
+ handler->response_handler = capabilities_response_handler;
+ handler->response_baton = &crb;
+ handler->session = serf_sess;
+ handler->conn = serf_sess->conns[0];
+
+ printf("KFF: about to create request\n");
+ fflush(stdout);
+ svn_ra_serf__request_create(handler);
+ printf("KFF: created request\n");
+ fflush(stdout);
+
+ printf("KFF: about to run_wait\n");
+ fflush(stdout);
+ SVN_ERR(svn_ra_serf__context_run_wait(&(crb.done), serf_sess, pool));
+ printf("KFF: done running_waiting\n");
+ fflush(stdout);
+
+ printf("KFF: in serf, done asking about '%s'\n", capability);
+ fflush(stdout);
+
+ *has = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
static const svn_ra__vtable_t serf_vtable = {
ra_serf_version,
ra_serf_get_description,
@@ -765,6 +863,7 @@
svn_ra_serf__get_lock,
svn_ra_serf__get_locks,
svn_ra_serf__replay,
+ svn_ra_serf__has_capability
};
svn_error_t *
Index: subversion/libsvn_ra_neon/session.c
===================================================================
--- subversion/libsvn_ra_neon/session.c (revision 27149)
+++ subversion/libsvn_ra_neon/session.c (working copy)
@@ -788,6 +788,7 @@
ras->compression = compression;
ras->progress_baton = callbacks->progress_baton;
ras->progress_func = callbacks->progress_func;
+ ras->capabilities = apr_hash_make(session->pool);
/* save config and server group in the auth parameter hash */
svn_auth_set_parameter(ras->callbacks->auth_baton,
SVN_AUTH_PARAM_CONFIG, cfg);
@@ -962,6 +963,145 @@
}
+/* The only two possible values for a capability. */
+static const char *capability_yes = "yes";
+static const char *capability_no = "no";
+
+
+/* Store in RAS the capabilities discovered from REQ's headers.
+ Use POOL for temporary allocation only. */
+static void
+parse_capabilities(ne_request *req,
+ svn_ra_neon__session_t *ras,
+ apr_pool_t *pool)
+{
+ void *ne_header_cursor = NULL;
+
+ /* Start out assuming all capabilities are unsupported. */
+ apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ APR_HASH_KEY_STRING, capability_no);
+
+ /* Then find out which ones are supported. */
+ do {
+ const char *header_name;
+ const char *header_value;
+ ne_header_cursor = ne_response_header_iterate(req,
+ ne_header_cursor,
+ &header_name,
+ &header_value);
+ if (ne_header_cursor && strcasecmp(header_name, "dav") == 0)
+ {
+ /* By the time we get the headers, Neon has downcased them and
+ merged them together -- merged in the sense that if a
+ header "foo" appears multiple times, all the values will be
+ concatenated together, with spaces at the splice points.
+ For example, if the server sent:
+
+ DAV: version-control,checkout,working-resource
+ DAV: merge,baseline,activity,version-controlled-collection
+ DAV: http://subversion.tigris.org/xmlns/dav/svn/depth
+
+ Here we might see:
+
+ header_name == "dav"
+ header_value == "1,2, version-control,checkout,working-resource, merge,baseline,activity,version-controlled-collection, http://subversion.tigris.org/xmlns/dav/svn/depth, <http://apache.org/dav/propset/fs/1>"
+
+ (Deliberately not line-wrapping that, so you can see what
+ we're about to parse.)
+ */
+
+ apr_array_header_t *vals =
+ svn_cstring_split(header_value, ",", TRUE, pool);
+
+ /* Right now the only capability we detect is depth, so just
+ seek for it directly. This could be rewritten as an
+ iteration with a switch-case inside, or whatever, if we
+ ever need to detect other capabilities, though. */
+
+ if (svn_cstring_match_glob_list(SVN_DAV_PROP_NS_DAV_SVN_DEPTH, vals))
+ apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ APR_HASH_KEY_STRING, capability_yes);
+ }
+ } while (ne_header_cursor);
+}
+
+
+/* Fill RAS->capabilities with the server's capabilities. Use
+ POOL only for temporary allocation. */
+static svn_error_t *
+discover_capabilities (svn_ra_neon__session_t *ras, apr_pool_t *pool)
+{
+ int http_ret_code;
+ svn_ra_neon__request_t *rar;
+
+ rar = svn_ra_neon__request_create(ras, "OPTIONS", ras->repos_root, pool);
+ SVN_ERR(svn_ra_neon__request_dispatch(&http_ret_code, rar,
+ NULL, NULL, 200, 0, pool));
+
+ if (http_ret_code == 200)
+ {
+ parse_capabilities(rar->ne_req, ras, pool);
+ }
+ else
+ {
+ /* "can't happen", because svn_ra_neon__request_dispatch()
+ itself should have returned error if response code != 200. */
+ return svn_error_createf
+ (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("OPTIONS request (for capabilities) got HTTP response code %d"),
+ http_ret_code);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_neon__has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_neon__session_t *ras = session->priv;
+ const char *cap_result = apr_hash_get(ras->capabilities,
+ SVN_RA_CAPABILITY_DEPTH,
+ APR_HASH_KEY_STRING);
+
+ /* If any capability is unknown, they're all unknown, so ask. */
+ if (cap_result == NULL)
+ discover_capabilities(ras, pool);
+
+ /* Try again, now that we've fetched the capabilities. */
+ cap_result = apr_hash_get(ras->capabilities,
+ SVN_RA_CAPABILITY_DEPTH, APR_HASH_KEY_STRING);
+
+ 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_RA_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;
+}
+
+
static const svn_version_t *
ra_neon_version(void)
{
@@ -1001,6 +1141,7 @@
svn_ra_neon__get_lock,
svn_ra_neon__get_locks,
svn_ra_neon__replay,
+ svn_ra_neon__has_capability
};
svn_error_t *
Index: subversion/libsvn_ra_neon/ra_neon.h
===================================================================
--- subversion/libsvn_ra_neon/ra_neon.h (revision 27149)
+++ subversion/libsvn_ra_neon/ra_neon.h (working copy)
@@ -100,6 +100,14 @@
svn_ra_progress_notify_func_t progress_func;
void *progress_baton;
+
+ /* Maps SVN_RA_CAPABILITY_foo keys to "yes" or "no" values.
+ If a capability is not yet discovered, it is absent from the table.
+ The table itself is allocated in the svn_ra_session_t's pool;
+ keys and values must have at least that lifetime. Most likely
+ the keys and values are constants anyway (and internal code may
+ just compare against those constants' addresses, therefore). */
+ apr_hash_t *capabilities;
} svn_ra_neon__session_t;
@@ -999,6 +1007,15 @@
void *edit_baton,
apr_pool_t *pool);
+/*
+ * Implements the has_capability RA layer function. */
+svn_error_t *
+svn_ra_neon__has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+
/* Helper function. Loop over LOCK_TOKENS and assemble all keys and
values into a stringbuf allocated in POOL. The string will be of
the form
Index: subversion/mod_dav_svn/version.c
===================================================================
--- subversion/mod_dav_svn/version.c (revision 27149)
+++ subversion/mod_dav_svn/version.c (working copy)
@@ -133,8 +133,8 @@
"version-control,checkout,working-resource");
apr_text_append(p, phdr,
"merge,baseline,activity,version-controlled-collection");
-
/* ### fork-control? */
+ apr_text_append(p, phdr, SVN_DAV_PROP_NS_DAV_SVN_DEPTH);
}
Index: subversion/libsvn_ra_svn/client.c
===================================================================
--- subversion/libsvn_ra_svn/client.c (revision 27149)
+++ subversion/libsvn_ra_svn/client.c (working copy)
@@ -2095,6 +2095,31 @@
}
+static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+
+ if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0)
+ {
+ if (svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_DEPTH))
+ *has = TRUE;
+ else
+ *has = FALSE;
+ }
+ else /* Don't know any other capabilities yet, so error. */
+ {
+ return svn_error_createf
+ (SVN_ERR_RA_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"), capability);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
static const svn_ra__vtable_t ra_svn_vtable = {
svn_ra_svn_version,
ra_svn_get_description,
@@ -2128,6 +2153,7 @@
ra_svn_get_lock,
ra_svn_get_locks,
ra_svn_replay,
+ ra_svn_has_capability
};
svn_error_t *
Index: subversion/libsvn_ra_svn/protocol
===================================================================
--- subversion/libsvn_ra_svn/protocol (revision 27149)
+++ subversion/libsvn_ra_svn/protocol (working copy)
@@ -188,6 +188,9 @@
See section 3.1.1.
[S] mergeinfo If the server presents this capability, it supports the
get-mergeinfo command. See section 3.1.1.
+[S] depth If the server presents this capability, it understands
+ requested operational depth (see section 3.1.1) and
+ per-path ambient depth (see section 3.1.3).
3. Commands
-----------
Index: subversion/svnserve/serve.c
===================================================================
--- subversion/svnserve/serve.c (revision 27149)
+++ subversion/svnserve/serve.c (working copy)
@@ -2450,13 +2450,14 @@
/* Send greeting. We don't support version 1 any more, so we can
* send an empty mechlist. */
- SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwww)",
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwww)",
(apr_uint64_t) 2, (apr_uint64_t) 2,
SVN_RA_SVN_CAP_EDIT_PIPELINE,
SVN_RA_SVN_CAP_SVNDIFF1,
SVN_RA_SVN_CAP_ABSENT_ENTRIES,
SVN_RA_SVN_CAP_COMMIT_REVPROPS,
- SVN_RA_SVN_CAP_MERGEINFO));
+ SVN_RA_SVN_CAP_MERGEINFO,
+ SVN_RA_SVN_CAP_DEPTH));
/* Read client response, which we assume to be in version 2 format:
* version, capability list, and client URL; then we do an auth