Justin, do you see anything obviously wrong in how I'm going about this? The patch is huge, but only the changes in libsvn_ra_serf/ are relevant here. I've put some debugging printf()s (with "KFF" as the unique string) in the code for now. Right now, it gets this result: $ svn co --depth=empty http://svn.collab.net/repos/svn/trunk/notes old-wc KFF: in serf, asking about 'depth' KFF: about to create request KFF: created request KFF: about to run_wait subversion/libsvn_ra_serf/util.c:334: (apr_err=20014) svn: Error running context: Internal error $ [[[ 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, " + + (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