Index: subversion/libsvn_ra/compat.c =================================================================== --- subversion/libsvn_ra/compat.c (.../trunk) (revision 33886) +++ subversion/libsvn_ra/compat.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -777,3 +777,93 @@ /* Reparent the session back to the original URL. */ return svn_ra_reparent(ra_session, session_url, pool); } + + +/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/ + +/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */ +typedef struct log_path_del_rev_t +{ + /* Absolute repository path. */ + const char *path; + + /* Revision PATH was first deleted or replaced. */ + svn_revnum_t revision_deleted; +} log_path_del_rev_t; + +/* A svn_log_entry_receiver_t callback for finding the revision + ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced. + Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED. + */ +svn_error_t * +log_path_del_receiver(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, log_entry->changed_paths); + hi != NULL; + hi = apr_hash_next(hi)) + { + void *val; + char *path; + svn_log_changed_path_t *log_item; + + apr_hash_this(hi, (void *) &path, NULL, &val); + log_item = val; + if (svn_path_compare_paths(((log_path_del_rev_t *) baton)->path, + path) == 0 + && (log_item->action == 'D' + || log_item->action == 'R')) + { + /* Found the first deletion or replacement, we are done. */ + ((log_path_del_rev_t *) baton)->revision_deleted = + log_entry->revision; + break; + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session, + const char *rel_deleted_path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + const char *session_url, *source_root_url, *rel_path_url, *abs_del_path; + log_path_del_rev_t log_path_deleted_baton; + + SVN_ERR_ASSERT(*rel_deleted_path != '/'); + + if (!SVN_IS_VALID_REVNUM(peg_revision)) + return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid peg revision %ld"), peg_revision); + if (!SVN_IS_VALID_REVNUM(end_revision)) + return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid end revision %ld"), end_revision); + if (end_revision <= peg_revision) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Peg revision must precede end revision")); + + SVN_ERR(svn_ra_get_session_url(session, &session_url, pool)); + SVN_ERR(svn_ra_get_repos_root2(session, &source_root_url, pool)); + rel_path_url = svn_path_url_add_component(session_url, rel_deleted_path, + pool); + abs_del_path = svn_path_uri_decode(rel_path_url + strlen(source_root_url), pool); + log_path_deleted_baton.path = abs_del_path; + log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM; + + /* Examine the logs of SESSION's URL to find when DELETED_PATH was first + deleted or replaced. */ + SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0, + TRUE, TRUE, FALSE, + apr_array_make(pool, 0, sizeof(char *)), + log_path_del_receiver, &log_path_deleted_baton, + pool)); + *revision_deleted = log_path_deleted_baton.revision_deleted; + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra/ra_loader.c =================================================================== --- subversion/libsvn_ra/ra_loader.c (.../trunk) (revision 33886) +++ subversion/libsvn_ra/ra_loader.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -1402,6 +1402,46 @@ return session->vtable->has_capability(session, has, capability, pool); } +svn_error_t * +svn_ra_get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + svn_error_t *err; + + /* Path must be relative. */ + SVN_ERR_ASSERT(*path != '/'); + + if (!SVN_IS_VALID_REVNUM(peg_revision)) + return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid peg revision %ld"), peg_revision); + if (!SVN_IS_VALID_REVNUM(end_revision)) + return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid end revision %ld"), end_revision); + if (end_revision <= peg_revision) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Peg revision must precede end revision")); + err = session->vtable->get_deleted_rev(session, path, + peg_revision, + end_revision, + revision_deleted, + pool); + if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE /* serf */ + || err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)) /* neon */ + { + svn_error_clear(err); + + /* Do it the slow way, using get-logs, for older servers. */ + err = svn_ra__get_deleted_rev_from_log(session, path, peg_revision, + end_revision, revision_deleted, + pool); + } + return err; +} + svn_error_t * Index: subversion/libsvn_ra/ra_loader.h =================================================================== --- subversion/libsvn_ra/ra_loader.h (.../trunk) (revision 33886) +++ subversion/libsvn_ra/ra_loader.h (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -247,6 +247,12 @@ svn_ra_replay_revfinish_callback_t revfinish_func, void *replay_baton, apr_pool_t *pool); + svn_error_t *(*get_deleted_rev)(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool); } svn_ra__vtable_t; /* The RA session object. */ @@ -377,6 +383,33 @@ void *handler_baton, apr_pool_t *pool); + +/** + * Given a path REL_DELETED_PATH, relative to the URL of SESSION, which + * exists at PEG_REVISION, and an END_REVISION > PEG_REVISION at which + * REL_DELETED_PATH no longer exists, set *REVISION_DELETED to the revision + * REL_DELETED_PATH was first deleted or replaced, within the inclusive + * revision range defined by PEG_REVISION and END_REVISION. + * + * If REL_DELETED_PATH does not exist at PEG_REVISION or was not deleted prior + * to END_REVISION within the specified range, then set *REVISION_DELETED to + * SVN_INVALID_REVNUM. If PEG_REVISION or END_REVISION are invalid or if + * END_REVISION <= PEG_REVISION, then return SVN_ERR_CLIENT_BAD_REVISION. + * + * Use POOL for all allocations. + * + * NOTE: This function uses the RA get_log interfaces to do its work, + * as a fallback mechanism for servers which don't support the native + * get_deleted_rev API. + */ +svn_error_t * +svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session, + const char *rel_deleted_path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool); + #ifdef __cplusplus } #endif Index: subversion/libsvn_ra_local/ra_plugin.c =================================================================== --- subversion/libsvn_ra_local/ra_plugin.c (.../trunk) (revision 33886) +++ subversion/libsvn_ra_local/ra_plugin.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -1397,6 +1397,27 @@ return SVN_NO_ERROR; } +static svn_error_t * +svn_ra_local__get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + svn_ra_local__session_baton_t *sess = session->priv; + const char *abs_path = svn_path_join(sess->fs_path->data, path, pool); + + SVN_ERR(svn_repos_deleted_rev(sess->fs, + abs_path, + peg_revision, + end_revision, + revision_deleted, + pool)); + + return SVN_NO_ERROR; +} + /*----------------------------------------------------------------*/ static const svn_version_t * @@ -1442,7 +1463,8 @@ svn_ra_local__get_locks, svn_ra_local__replay, svn_ra_local__has_capability, - svn_ra_local__replay_range + svn_ra_local__replay_range, + svn_ra_local__get_deleted_rev }; Index: subversion/mod_dav_svn/dav_svn.h =================================================================== --- subversion/mod_dav_svn/dav_svn.h (.../trunk) (revision 33886) +++ subversion/mod_dav_svn/dav_svn.h (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -520,6 +520,7 @@ { SVN_XML_NAMESPACE, "file-revs-report" }, { SVN_XML_NAMESPACE, "get-locks-report" }, { SVN_XML_NAMESPACE, "replay-report" }, + { SVN_XML_NAMESPACE, "get-deleted-rev-report" }, { SVN_XML_NAMESPACE, SVN_DAV__MERGEINFO_REPORT }, { NULL, NULL }, }; @@ -563,6 +564,10 @@ const apr_xml_doc *doc, ap_filter_t *output); +dav_error * +dav_svn__get_deleted_rev_report(const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output); /*** authz.c ***/ Index: subversion/mod_dav_svn/version.c =================================================================== --- subversion/mod_dav_svn/version.c (.../trunk) (revision 33886) +++ subversion/mod_dav_svn/version.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -984,7 +984,10 @@ { return dav_svn__get_mergeinfo_report(resource, doc, output); } - + else if (strcmp(doc->root->name, "get-deleted-rev-report") == 0) + { + return dav_svn__get_deleted_rev_report(resource, doc, output); + } /* NOTE: if you add a report, don't forget to add it to the * dav_svn__reports_list[] array. */ Index: subversion/mod_dav_svn/reports/deleted-rev.c =================================================================== --- subversion/mod_dav_svn/reports/deleted-rev.c (.../trunk) (revision 0) +++ subversion/mod_dav_svn/reports/deleted-rev.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -0,0 +1,132 @@ +/* + * deleted-rev : routine for getting the revision a path was deleted. + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include + +#include +#include + +#include "svn_xml.h" +#include "svn_repos.h" +#include "svn_dav.h" +#include "svn_pools.h" + +#include "private/svn_dav_protocol.h" + +#include "../dav_svn.h" + +/* Respond to a S:deleted-rev-report request. */ +dav_error * +dav_svn__get_deleted_rev_report(const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output) +{ + apr_xml_elem *child; + int ns; + const char *rel_path, *abs_path; + svn_revnum_t peg_rev, end_rev, deleted_rev; + apr_bucket_brigade *bb; + svn_error_t *err; + apr_status_t apr_err; + dav_error *derr = NULL; + + /* Sanity check. */ + ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE); + if (ns == -1) + return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, + "The request does not contain the 'svn:' " + "namespace, so it is not going to have " + "certain required elements.", + SVN_DAV_ERROR_NAMESPACE, + SVN_DAV_ERROR_TAG); + + for (child = doc->root->first_child; child != NULL; child = child->next) + { + /* If this element isn't one of ours, then skip it. */ + if (child->ns != ns ) + continue; + + if (strcmp(child->name, "peg-revision") == 0) + { + peg_rev = SVN_STR_TO_REV(dav_xml_get_cdata(child, + resource->pool, 1)); + } + else if (strcmp(child->name, "end-revision") == 0) + { + end_rev = SVN_STR_TO_REV(dav_xml_get_cdata(child, + resource->pool, 1)); + } + else if (strcmp(child->name, "path") == 0) + { + rel_path = dav_xml_get_cdata(child, resource->pool, 0); + if ((derr = dav_svn__test_canonical(rel_path, resource->pool))) + return derr; + } + } + + /* Check that all parameters are present. */ + if (! (rel_path + && SVN_IS_VALID_REVNUM(peg_rev) + && SVN_IS_VALID_REVNUM(end_rev))) + { + return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, + "Not all parameters passed.", + SVN_DAV_ERROR_NAMESPACE, + SVN_DAV_ERROR_TAG); + } + + /* Append the relative path to the base FS path to get an absolute + repository path. */ + abs_path = svn_path_join(resource->info->repos_path, rel_path, + resource->pool); + + /* Do what we actually came here for: Find the rev abs_path was deleted. */ + err = svn_repos_deleted_rev(resource->info->repos->fs, + abs_path, peg_rev, end_rev, + &deleted_rev, resource->pool); + if (err) + { + svn_error_clear(err); + return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + "Could not find revision path was deleted."); + } + + bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); + apr_err = ap_fprintf(output, bb, + DAV_XML_HEADER DEBUG_CR + "" DEBUG_CR + "%ld""", + deleted_rev); + if (apr_err) + derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL), + HTTP_INTERNAL_SERVER_ERROR, + "Error writing REPORT response.", + resource->pool); + + /* Flush the contents of the brigade (returning an error only if we + don't already have one). */ + if (((apr_err = ap_fflush(output, bb))) && (! derr)) + derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL), + HTTP_INTERNAL_SERVER_ERROR, + "Error flushing brigade.", + resource->pool); + + return derr; +} Index: subversion/libsvn_ra_svn/client.c =================================================================== --- subversion/libsvn_ra_svn/client.c (.../trunk) (revision 33886) +++ subversion/libsvn_ra_svn/client.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -2278,6 +2278,30 @@ return SVN_NO_ERROR; } +static svn_error_t * +ra_svn_get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) + +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + + /* Transmit the parameters. */ + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-deleted-rev", "crr", + path, peg_revision, end_revision)); + + /* Servers before 1.6 don't support this command. Check for this here. */ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + _("'get-deleted-rev' not implemented"))); + + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", revision_deleted)); + return SVN_NO_ERROR; +} + static const svn_ra__vtable_t ra_svn_vtable = { svn_ra_svn_version, @@ -2314,6 +2338,7 @@ ra_svn_replay, ra_svn_has_capability, ra_svn_replay_range, + ra_svn_get_deleted_rev, }; svn_error_t * Index: subversion/libsvn_ra_svn/protocol =================================================================== --- subversion/libsvn_ra_svn/protocol (.../trunk) (revision 33886) +++ subversion/libsvn_ra_svn/protocol (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -446,6 +446,10 @@ (revprops here is the literal word "revprops".) response ( ) + get-deleted-rev + params: ( path:string peg-rev:number end-rev:number ) + response: ( deleted-rev:number ) + 3.1.2. Editor Command Set An edit operation produces only one response, at close-edit or Index: subversion/include/svn_ra.h =================================================================== --- subversion/include/svn_ra.h (.../trunk) (revision 33886) +++ subversion/include/svn_ra.h (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -1758,6 +1758,29 @@ apr_pool_t *pool); /** + * Given @a path at revision @a peg_revision, set @a *revision_deleted to the + * revision @a path was first deleted, within the inclusive revision range + * defined by @a peg_revision and @a end_revision. @a path is relative + * to the URL in @a session. + * + * If @a path does not exist at @a peg_revision or was not deleted within + * the specified range, then set @a *revision_deleted to @c SVN_INVALID_REVNUM. + * If @a peg_revision or @a end_revision are invalid or if @a peg_revision is + * greater than @a end_revision, then return @c SVN_ERR_CLIENT_BAD_REVISION. + * + * Use @a pool for all allocations. + * + * @since New in 1.6. + */ +svn_error_t * +svn_ra_get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + 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.) Index: subversion/libsvn_ra_serf/serf.c =================================================================== --- subversion/libsvn_ra_serf/serf.c (.../trunk) (revision 33886) +++ subversion/libsvn_ra_serf/serf.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -1146,6 +1146,7 @@ svn_ra_serf__replay, svn_ra_serf__has_capability, svn_ra_serf__replay_range, + svn_ra_serf__get_deleted_rev }; svn_error_t * Index: subversion/libsvn_ra_serf/get_deleted_rev.c =================================================================== --- subversion/libsvn_ra_serf/get_deleted_rev.c (.../trunk) (revision 0) +++ subversion/libsvn_ra_serf/get_deleted_rev.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -0,0 +1,243 @@ +/* + * get_deleted_rev.c : ra_serf get_deleted_rev API implementation. + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + + +#include "svn_ra.h" +#include "svn_xml.h" +#include "svn_path.h" +#include "svn_private_config.h" + +#include "../libsvn_ra/ra_loader.h" + +#include "ra_serf.h" + + +/* + * This enum represents the current state of our XML parsing for a REPORT. + */ +typedef enum { + NONE = 0, + VERSION_NAME, +} drev_state_e; + +typedef struct { + apr_pool_t *pool; + + const char *path; + svn_revnum_t peg_revision; + svn_revnum_t end_revision; + + /* What revision was PATH@PEG_REVISION first deleted within + the range PEG_REVISION-END-END_REVISION? */ + svn_revnum_t *revision_deleted; + + /* are we done? */ + svn_boolean_t done; + +} drev_context_t; + + +static svn_string_t * +push_state(svn_ra_serf__xml_parser_t *parser, + drev_context_t *drev_ctx, + drev_state_e state) +{ + svn_ra_serf__xml_push_state(parser, state); + + if (state == VERSION_NAME) + { + svn_string_t *info = apr_pcalloc(parser->state->pool, sizeof(*info)); + + parser->state->private = info; + } + + return parser->state->private; +} + +static svn_error_t * +start_getdrev(svn_ra_serf__xml_parser_t *parser, + void *userData, + svn_ra_serf__dav_props_t name, + const char **attrs) +{ + drev_context_t *drev_ctx = userData; + drev_state_e state; + + state = parser->state->current_state; + + if (state == NONE && + strcmp(name.name, SVN_DAV__VERSION_NAME) == 0) + { + push_state(parser, drev_ctx, VERSION_NAME); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +end_getdrev(svn_ra_serf__xml_parser_t *parser, + void *userData, + svn_ra_serf__dav_props_t name) +{ + drev_context_t *drev_ctx = userData; + drev_state_e state; + svn_string_t *info; + + state = parser->state->current_state; + info = parser->state->private; + + if (state == VERSION_NAME && + strcmp(name.name, SVN_DAV__VERSION_NAME) == 0) + { + *drev_ctx->revision_deleted = SVN_STR_TO_REV(info->data); + svn_ra_serf__xml_pop_state(parser); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +cdata_getdrev(svn_ra_serf__xml_parser_t *parser, + void *userData, + const char *data, + apr_size_t len) +{ + drev_context_t *drev_ctx = userData; + drev_state_e state; + svn_string_t **info; + + UNUSED_CTX(drev_ctx); + + state = parser->state->current_state; + info = &((svn_string_t *)parser->state->private); + + switch (state) + { + case VERSION_NAME: + *info = svn_string_ncreate(data, len, parser->state->pool); + break; + default: + break; + } + + return SVN_NO_ERROR; +} + +#define GETDREV_HEADER "" +#define GETDREV_FOOTER "" + +static serf_bucket_t* +create_getdrev_body(void *baton, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool) +{ + serf_bucket_t *buckets, *tmp; + drev_context_t *drev_ctx = baton; + + buckets = serf_bucket_aggregate_create(alloc); + + tmp = SERF_BUCKET_SIMPLE_STRING_LEN(GETDREV_HEADER, + sizeof(GETDREV_HEADER) - 1, + alloc); + serf_bucket_aggregate_append(buckets, tmp); + + svn_ra_serf__add_tag_buckets(buckets, + "S:path", drev_ctx->path, + alloc); + + svn_ra_serf__add_tag_buckets(buckets, + "S:peg-revision", + apr_ltoa(pool, drev_ctx->peg_revision), + alloc); + + svn_ra_serf__add_tag_buckets(buckets, + "S:end-revision", + apr_ltoa(pool, drev_ctx->end_revision), + alloc); + + tmp = SERF_BUCKET_SIMPLE_STRING_LEN(GETDREV_FOOTER, + sizeof(GETDREV_FOOTER)-1, + alloc); + serf_bucket_aggregate_append(buckets, tmp); + + return buckets; +} + +svn_error_t * +svn_ra_serf__get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + drev_context_t *drev_ctx; + svn_ra_serf__session_t *ras = session->priv; + svn_ra_serf__handler_t *handler; + svn_ra_serf__xml_parser_t *parser_ctx; + const char *relative_url, *basecoll_url, *req_url; + int status_code = 0; + svn_error_t *err; + + drev_ctx = apr_pcalloc(pool, sizeof(*drev_ctx)); + drev_ctx->path = path; + drev_ctx->peg_revision = peg_revision; + drev_ctx->end_revision = end_revision; + drev_ctx->pool = pool; + drev_ctx->revision_deleted = revision_deleted; + drev_ctx->done = FALSE; + + SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, + ras, NULL, peg_revision, NULL, + pool)); + + req_url = svn_path_url_add_component(basecoll_url, relative_url, pool); + + parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx)); + parser_ctx->pool = pool; + parser_ctx->user_data = drev_ctx; + parser_ctx->start = start_getdrev; + parser_ctx->end = end_getdrev; + parser_ctx->cdata = cdata_getdrev; + parser_ctx->done = &drev_ctx->done; + parser_ctx->status_code = &status_code; + + handler = apr_pcalloc(pool, sizeof(*handler)); + handler->method = "REPORT"; + handler->path = req_url; + handler->body_type = "text/xml"; + handler->response_handler = svn_ra_serf__handle_xml_parser; + handler->body_delegate = create_getdrev_body; + handler->body_delegate_baton = drev_ctx; + handler->conn = ras->conns[0]; + handler->session = ras; + handler->response_baton = parser_ctx; + + svn_ra_serf__request_create(handler); + + err = svn_ra_serf__context_run_wait(&drev_ctx->done, ras, pool); + + /* Map status 501: Method Not Implemented to our not implemented error. + 1.5.x servers and older don't support this report. */ + if (status_code == 501) + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, + _("'get-deleted-rev' REPORT not implemented")); + SVN_ERR(err); + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_serf/ra_serf.h =================================================================== --- subversion/libsvn_ra_serf/ra_serf.h (.../trunk) (revision 33886) +++ subversion/libsvn_ra_serf/ra_serf.h (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -1242,6 +1242,15 @@ const char *capability, apr_pool_t *pool); +/* Implements the get_deleted_rev RA layer function. */ +svn_error_t * +svn_ra_serf__get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool); + /*** Authentication handler declarations ***/ /** Index: subversion/libsvn_ra_neon/get_deleted_rev.c =================================================================== --- subversion/libsvn_ra_neon/get_deleted_rev.c (.../trunk) (revision 0) +++ subversion/libsvn_ra_neon/get_deleted_rev.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -0,0 +1,171 @@ +/* + * get_deleted_rev.c : ra_neon get_deleted_rev API implementation. + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include +#include +#include + +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_ra.h" +#include "../libsvn_ra/ra_loader.h" +#include "svn_path.h" +#include "svn_xml.h" + +#include "private/svn_dav_protocol.h" +#include "svn_private_config.h" + +#include "ra_neon.h" + +/* ------------------------------------------------------------------------- + * + * DELETED REV REPORT HANDLING + * + * The get-deleted-rev-report XML request body is quite straightforward: + * + * + * ... + * ... + * ... + * + * + * The response is simply a DAV:version-name element giving the revision + * path@peg-revision was first deleted up to end-revision or SVN_INVALID_REVNUM + * if it was never deleted: + * + * + * ... + * + */ + +/* Elements used in a get-deleted-rev-report response. */ +static const svn_ra_neon__xml_elm_t drev_report_elements[] = +{ + { SVN_XML_NAMESPACE, "get-deleted-rev-report", ELEM_deleted_rev_report, 0 }, + { "DAV:", "version-name", ELEM_version_name, SVN_RA_NEON__XML_CDATA }, + { NULL } +}; + +/* Context for parsing server's response. */ +typedef struct +{ + svn_stringbuf_t *cdata; + svn_revnum_t revision; + apr_pool_t *pool; +} drev_baton_t; + + +/* This implements the 'svn_ra_neon__startelm_cb_t' prototype. */ +static svn_error_t * +drev_start_element(int *elem, void *baton, int parent, + const char *nspace, const char *name, const char **atts) +{ + const svn_ra_neon__xml_elm_t *elm = + svn_ra_neon__lookup_xml_elem(drev_report_elements, nspace, name); + drev_baton_t *b = baton; + + *elem = elm ? elm->id : SVN_RA_NEON__XML_DECLINE; + if (!elm) + return SVN_NO_ERROR; + + if (elm->id == ELEM_version_name) + b->cdata = svn_stringbuf_create("", b->pool); + + return SVN_NO_ERROR; +} + +/* This implements the 'svn_ra_neon__endelm_cb_t' prototype. */ +static svn_error_t * +drev_end_element(void *baton, int state, + const char *nspace, const char *name) +{ + drev_baton_t *b = baton; + + if (state == ELEM_version_name && b->cdata) + { + b->revision = SVN_STR_TO_REV(b->cdata->data); + b->cdata = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_neon__get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + svn_ra_neon__session_t *ras = session->priv; + const char *url = svn_path_url_add_component(ras->url->data, path, pool); + const char *body, *final_bc_url; + svn_string_t bc_url, bc_relative; + int status_code; + svn_error_t *err; + drev_baton_t *b = apr_palloc(pool, sizeof(*b)); + + b->pool = pool; + b->cdata = NULL; + b->revision = SVN_INVALID_REVNUM; + + /* ras's URL may not exist in HEAD, and thus it's not safe to send + it as the main argument to the REPORT request; it might cause + dav_get_resource() to choke on the server. So instead, we pass a + baseline-collection URL, which we get from the peg revision. */ + SVN_ERR(svn_ra_neon__get_baseline_info(NULL, &bc_url, &bc_relative, NULL, + ras, ras->url->data, + peg_revision, + pool)); + final_bc_url = svn_path_url_add_component(bc_url.data, bc_relative.data, + pool); + + body = apr_psprintf(pool, + "" + "" + "%s" + "%s" + "%s" + "", + apr_xml_quote_string(pool, path, FALSE), + apr_psprintf(pool, "%ld", peg_revision), + apr_psprintf(pool, "%ld", end_revision)); + + /* Send the get-deleted-rev-report report request. There is no guarantee + that svn_ra_neon__parsed_request() will set status_code, so initialize + it. */ + status_code = 0; + err = svn_ra_neon__parsed_request(ras, "REPORT", final_bc_url, body, + NULL, NULL, + drev_start_element, + svn_ra_neon__xml_collect_cdata, + drev_end_element, + b, NULL, &status_code, FALSE, pool); + + /* Map status 501: Method Not Implemented to our not implemented error. + 1.5.x servers and older don't support this report. */ + if (status_code == 501) + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, + _("'get-deleted-rev' REPORT not implemented")); + + SVN_ERR(err); + *revision_deleted = b->revision; + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_neon/session.c =================================================================== --- subversion/libsvn_ra_neon/session.c (.../trunk) (revision 33886) +++ subversion/libsvn_ra_neon/session.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -1405,7 +1405,8 @@ svn_ra_neon__get_locks, svn_ra_neon__replay, svn_ra_neon__has_capability, - svn_ra_neon__replay_range + svn_ra_neon__replay_range, + svn_ra_neon__get_deleted_rev }; svn_error_t * Index: subversion/libsvn_ra_neon/ra_neon.h =================================================================== --- subversion/libsvn_ra_neon/ra_neon.h (.../trunk) (revision 33886) +++ subversion/libsvn_ra_neon/ra_neon.h (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -803,7 +803,8 @@ ELEM_mergeinfo_path, ELEM_mergeinfo_info, ELEM_has_children, - ELEM_merged_revision + ELEM_merged_revision, + ELEM_deleted_rev_report }; /* ### docco */ @@ -1052,6 +1053,15 @@ const char *capability, apr_pool_t *pool); +/* + * Implements the get_deleted_rev RA layer function. */ +svn_error_t * +svn_ra_neon__get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + 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 Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (.../trunk) (revision 33886) +++ subversion/svnserve/serve.c (.../branches/issue-3067-deleted-subtrees) (revision 33947) @@ -2712,6 +2712,29 @@ return SVN_NO_ERROR; } +static svn_error_t * +get_deleted_rev(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + apr_array_header_t *params, + void *baton) +{ + server_baton_t *b = baton; + const char *path, *full_path; + svn_revnum_t peg_revision; + svn_revnum_t end_revision; + svn_revnum_t revision_deleted; + + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crr", + &path, &peg_revision, &end_revision)); + full_path = svn_path_join(b->fs_path->data, + svn_path_canonicalize(path, pool), pool); + SVN_ERR(log_command(b, conn, pool, "get-deleted-rev")); + SVN_ERR(trivial_auth_request(conn, pool, b)); + SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision, + &revision_deleted, pool)); + SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", revision_deleted)); + return SVN_NO_ERROR; +} static const svn_ra_svn_cmd_entry_t main_commands[] = { { "reparent", reparent }, @@ -2742,6 +2765,7 @@ { "get-locks", get_locks }, { "replay", replay }, { "replay-range", replay_range }, + { "get-deleted-rev", get_deleted_rev }, { NULL } };