"C. Michael Pilato" <cmpil...@collab.net> writes: > Just a thought: Have you considered expanding the scope of the private > resource space rather than using the magic prefix hack? You could add > ".../!svn/vtxn/UUID" and ".../!svn/vtxr/UUID/..." to be alternate ways to > address transactions and transaction roots (the "v" there being a shortcut > for "virtual"). This is *effectively* the same approach as yours -- there's > a different prefix here. But the prefix is a clearly defined piece of the > protocol, not just some magic bit buried in mod_dav_svn's codebase.
This looks promising, it's quite straightforward to implement. New log/patch follows: Extend Subversion's v2 HTTP protocol to include URIs that allow the client to define the transaction name visible in on the wire. If the client sends, or a proxy injects, an SVN-VTxn-Name header with the POST request it defines the transaction name to be returned to the client in the POST response. If the client recieves the new SVN-VTxn-Name header it uses that name in the new URIs in the requests that make up the commit. By default the client will not send the new header. * subversion/mod_dav_svn/dav_svn.h (struct dav_svn_root): Add vtxn_name member. (dav_svn__get_vtxn_stub, dav_svn__get_vtxn_root_stub): New. * subversion/mod_dav_svn/mod_dav_svn.c (dav_svn__get_vtxn_stub, dav_svn__get_vtxn_root_stub): New. * subversion/mod_dav_svn/posts/create_txn.c (dav_svn__post_create_txn): Get vtxn_name from header, if sent, and store vtxn_name:txn_name mapping in the activity database. * subversion/mod_dav_svn/version.c (get_option): Add vtxn stub and vtxn root stub headers. (merge): Delete visible_txn_name:txn_name mapping after commit. * subversion/mod_dav_svn/repos.c (parse_vtxnstub_uri, parse_vtxnroot_uri): New. (special_subdirs): Add vtxn and vtxr. (remove_resource): Delete mapping when aborting the transaction. * subversion/libsvn_ra_serf/ra_serf.h (struct svn_ra_serf__session_t): Add vtxn_stub and vtxn_root_stub. * subversion/libsvn_ra_serf/commit.c (setup_post_headers): New. (post_headers_iterator_callback): Handle vtxn name header. (open_root): Set header_delegate. * subversion/libsvn_ra_serf/options.c (capabilities_headers_iterator_callback): Support vtxn stub and vtxn root stub headers. * subversion/libsvn_ra_neon/options.c (parse_capabilities): Support vtxn stub and vtxn root stub headers. * subversion/include/svn_dav.h (SVN_DAV_VTXN_STUB_HEADER, SVN_DAV_VTXN_ROOT_STUB_HEADER, SVN_DAV_VTXN_NAME_HEADER): New. * notes/http-and-webdav/http-protocol-v2.txt: Update. Index: notes/http-and-webdav/http-protocol-v2.txt =================================================================== --- notes/http-and-webdav/http-protocol-v2.txt (revision 1077861) +++ notes/http-and-webdav/http-protocol-v2.txt (working copy) @@ -128,6 +128,14 @@ Various read- and write-type requests can be issued against these resources (MKCOL, PUT, PROPFIND, PROPPATCH, GET, etc.). + - alternate transaction resource (!svn/vtxn/VTXN-NAME) + alternate transaction root resource (!svn/vtxr/VTXN-NAME/[PATH]) + + Alternative names for the transaction based on a virtual, or + visible, name supplied by the client when the transaction + was created. The client supplied name is optional, if not + supplied these resource names are not valid. + * Opening an RA session: ra_serf will send an OPTIONS request when creating a new @@ -220,12 +228,17 @@ - no more need to "discover" the activity URI; !svn/act/ is gone. - - client no longer creates an activity UUID itself. + - client no longer needs to create an activity UUID itself. - instead, POST returns the name of the transaction it created, - which can then be appended to the transaction stub and - transaction root stub as necessary. + as TXN-NAME, which can then be appended to the transaction + stub and transaction root stub as necessary. + - if the client does choose to supply a UUID with the POST + request then the POST returns that UUID as VTXN-NAME, and the + client then uses that with the alternate transaction stub and + transaction root stub in subsequent requests. + - Once the commit transaction is created, the client is free to send write requests against transaction resources it constructs itself. This eliminates the CHECKOUT requests, and also Index: subversion/mod_dav_svn/mod_dav_svn.c =================================================================== --- subversion/mod_dav_svn/mod_dav_svn.c (revision 1077861) +++ subversion/mod_dav_svn/mod_dav_svn.c (working copy) @@ -626,6 +626,22 @@ } +const char * +dav_svn__get_vtxn_stub(request_rec *r) +{ + return apr_pstrcat(r->pool, dav_svn__get_special_uri(r), "/vtxn", + (char *)NULL); +} + + +const char * +dav_svn__get_vtxn_root_stub(request_rec *r) +{ + return apr_pstrcat(r->pool, dav_svn__get_special_uri(r), "/vtxr", + (char *)NULL); +} + + svn_boolean_t dav_svn__get_autoversioning_flag(request_rec *r) { Index: subversion/mod_dav_svn/dav_svn.h =================================================================== --- subversion/mod_dav_svn/dav_svn.h (revision 1077861) +++ subversion/mod_dav_svn/dav_svn.h (working copy) @@ -202,6 +202,15 @@ */ const char *txn_name; + /* The optional vtxn name supplied by an HTTPv2 client and + used in subsequent requests. This may be NULL if the client + is not using a vtxn name. + + PRIVATE resources that directly represent either a txn or + txn-root use this field. + */ + const char *vtxn_name; + /* If the root is part of a transaction, this contains the FS's transaction handle. It may be NULL if this root corresponds to a specific revision. It may also be NULL if we have not opened the transaction yet. @@ -392,7 +401,13 @@ /* For accessing transaction properties (typically "!svn/txr") */ const char *dav_svn__get_txn_root_stub(request_rec *r); +/* For accessing transaction resources (typically "!svn/vtxn") */ +const char *dav_svn__get_vtxn_stub(request_rec *r); +/* For accessing transaction properties (typically "!svn/vtxr") */ +const char *dav_svn__get_vtxn_root_stub(request_rec *r); + + /*** activity.c ***/ /* Create a new transaction based on HEAD in REPOS, setting *PTXN_NAME Index: subversion/mod_dav_svn/version.c =================================================================== --- subversion/mod_dav_svn/version.c (revision 1077861) +++ subversion/mod_dav_svn/version.c (working copy) @@ -250,6 +250,12 @@ apr_table_set(r->headers_out, SVN_DAV_TXN_STUB_HEADER, apr_pstrcat(resource->pool, repos_root_uri, "/", dav_svn__get_txn_stub(r), (char *)NULL)); + apr_table_set(r->headers_out, SVN_DAV_VTXN_ROOT_STUB_HEADER, + apr_pstrcat(resource->pool, repos_root_uri, "/", + dav_svn__get_vtxn_root_stub(r), (char *)NULL)); + apr_table_set(r->headers_out, SVN_DAV_VTXN_STUB_HEADER, + apr_pstrcat(resource->pool, repos_root_uri, "/", + dav_svn__get_vtxn_stub(r), (char *)NULL)); } return NULL; @@ -1427,6 +1433,12 @@ svn_error_clear(serr); serr = SVN_NO_ERROR; } + + /* HTTPv2 doesn't send DELETE after a successful MERGE so if + using the optional vtxn name mapping then delete it here. */ + if (source->info->root.vtxn_name) + dav_svn__delete_activity(source->info->repos, + source->info->root.vtxn_name); } else { Index: subversion/mod_dav_svn/posts/create_txn.c =================================================================== --- subversion/mod_dav_svn/posts/create_txn.c (revision 1077861) +++ subversion/mod_dav_svn/posts/create_txn.c (working copy) @@ -37,6 +37,7 @@ ap_filter_t *output) { const char *txn_name; + const char *vtxn_name; dav_error *derr; request_rec *r = resource->info->r; @@ -47,7 +48,20 @@ /* Build a "201 Created" response with header that tells the client our new transaction's name. */ - apr_table_set(r->headers_out, SVN_DAV_TXN_NAME_HEADER, txn_name); + vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER); + if (vtxn_name && vtxn_name[0]) + { + /* If the client supplied a vtxn name then store a mapping from + the client name to the FS transaction name in the activity + database. */ + if ((derr = dav_svn__store_activity(resource->info->repos, + vtxn_name, txn_name))) + return derr; + apr_table_set(r->headers_out, SVN_DAV_VTXN_NAME_HEADER, vtxn_name); + } + else + apr_table_set(r->headers_out, SVN_DAV_TXN_NAME_HEADER, txn_name); + r->status = HTTP_CREATED; return NULL; Index: subversion/mod_dav_svn/repos.c =================================================================== --- subversion/mod_dav_svn/repos.c (revision 1077861) +++ subversion/mod_dav_svn/repos.c (working copy) @@ -491,7 +491,25 @@ return FALSE; } +static int +parse_vtxnstub_uri(dav_resource_combined *comb, + const char *path, + const char *label, + int use_checked_in) +{ + /* format: !svn/vtxn/TXN_NAME */ + if (parse_txnstub_uri(comb, path, label, use_checked_in)) + return TRUE; + + comb->priv.root.vtxn_name = comb->priv.root.txn_name; + comb->priv.root.txn_name = dav_svn__get_txn(comb->priv.repos, + comb->priv.root.vtxn_name); + + return FALSE; +} + + static int parse_txnroot_uri(dav_resource_combined *comb, const char *path, @@ -541,7 +559,25 @@ return FALSE; } +static int +parse_vtxnroot_uri(dav_resource_combined *comb, + const char *path, + const char *label, + int use_checked_in) +{ + /* format: !svn/vtxr/TXN_NAME/[PATH] */ + if (parse_txnroot_uri(comb, path, label, use_checked_in)) + return TRUE; + + comb->priv.root.vtxn_name = comb->priv.root.txn_name; + comb->priv.root.txn_name = dav_svn__get_txn(comb->priv.repos, + comb->priv.root.vtxn_name); + + return FALSE; +} + + static int parse_wrk_baseline_uri(dav_resource_combined *comb, const char *path, @@ -621,6 +657,8 @@ { "rvr", parse_revroot_uri, 1, TRUE, DAV_SVN_RESTYPE_REVROOT_COLLECTION }, { "txn", parse_txnstub_uri, 1, FALSE, DAV_SVN_RESTYPE_TXN_COLLECTION}, { "txr", parse_txnroot_uri, 1, TRUE, DAV_SVN_RESTYPE_TXNROOT_COLLECTION}, + { "vtxn", parse_vtxnstub_uri, 1, FALSE, DAV_SVN_RESTYPE_TXN_COLLECTION}, + { "vtxr", parse_vtxnroot_uri, 1, TRUE, DAV_SVN_RESTYPE_TXNROOT_COLLECTION}, { NULL } /* sentinel */ }; @@ -3783,11 +3821,13 @@ if (resource->type == DAV_RESOURCE_TYPE_PRIVATE && resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION) { - /* We'll assume that no activity was created to map to this - transaction. */ - return dav_svn__abort_txn(resource->info->repos, - resource->info->root.txn_name, - resource->pool); + if (resource->info->root.vtxn_name) + return dav_svn__delete_activity(resource->info->repos, + resource->info->root.vtxn_name); + else + return dav_svn__abort_txn(resource->info->repos, + resource->info->root.txn_name, + resource->pool); } /* ### note that the parent was checked out at some point, and this Index: subversion/include/svn_dav.h =================================================================== --- subversion/include/svn_dav.h (revision 1077861) +++ subversion/include/svn_dav.h (working copy) @@ -133,6 +133,11 @@ * from a "txn root"). (HTTP protocol v2 only) */ #define SVN_DAV_TXN_STUB_HEADER "SVN-Txn-Stub" +/** Companion to @c SVN_DAV_TXN_STUB_HEADER, used when a POST request + * returns @c SVN_DAV_VTXN_NAME_HEADER in response to a client + * supplied name. (HTTP protocol v2 only) */ +#define SVN_DAV_VTXN_STUB_HEADER "SVN-VTxn-Stub" + /** This header provides an opaque URI which represents the root * directory of a Subversion transaction (revision-in-progress), * similar to the concept of a "txn root" in the libsvn_fs API. The @@ -141,6 +146,11 @@ * protocol v2 only) */ #define SVN_DAV_TXN_ROOT_STUB_HEADER "SVN-Txn-Root-Stub" +/** Companion to @c SVN_DAV_TXN_ROOT_STUB_HEADER, used when a POST + * request returns @c SVN_DAV_VTXN_NAME_HEADER in response to a + * client supplied name. (HTTP protocol v2 only) */ +#define SVN_DAV_VTXN_ROOT_STUB_HEADER "SVN-VTxn-Root-Stub" + /** This header is used in the POST response to tell the client the * name of the Subversion transaction created by the request. It can * then be appended to the transaction stub and transaction root stub @@ -148,6 +158,12 @@ * transaction. (HTTP protocol v2 only) */ #define SVN_DAV_TXN_NAME_HEADER "SVN-Txn-Name" +/** This header is used in the POST request, to pass a client supplied + * alternative transaction name to the server, and in the the POST + * response, to tell the client that the alternative transaction + * resource names should be used. (HTTP protocol v2 only) */ +#define SVN_DAV_VTXN_NAME_HEADER "SVN-VTxn-Name" + /** * @name Fulltext MD5 headers * Index: subversion/libsvn_ra_neon/ra_neon.h =================================================================== --- subversion/libsvn_ra_neon/ra_neon.h (revision 1077861) +++ subversion/libsvn_ra_neon/ra_neon.h (working copy) @@ -146,6 +146,8 @@ const char *rev_root_stub; /* for accessing REV/PATH pairs */ const char *txn_stub; /* for accessing transactions (i.e. txnprops) */ const char *txn_root_stub; /* for accessing TXN/PATH pairs */ + const char *vtxn_stub; /* for accessing transactions (i.e. txnprops) */ + const char *vtxn_root_stub; /* for accessing TXN/PATH pairs */ /*** End HTTP v2 stuff ***/ Index: subversion/libsvn_ra_neon/options.c =================================================================== --- subversion/libsvn_ra_neon/options.c (revision 1077861) +++ subversion/libsvn_ra_neon/options.c (working copy) @@ -253,6 +253,14 @@ { ras->txn_stub = apr_pstrdup(ras->pool, val); } + if ((val = ne_get_response_header(req, SVN_DAV_VTXN_ROOT_STUB_HEADER))) + { + ras->vtxn_root_stub = apr_pstrdup(ras->pool, val); + } + if ((val = ne_get_response_header(req, SVN_DAV_VTXN_STUB_HEADER))) + { + ras->vtxn_stub = apr_pstrdup(ras->pool, val); + } } Index: subversion/libsvn_ra_serf/ra_serf.h =================================================================== --- subversion/libsvn_ra_serf/ra_serf.h (revision 1077861) +++ subversion/libsvn_ra_serf/ra_serf.h (working copy) @@ -195,6 +195,8 @@ const char *rev_root_stub; /* for accessing REV/PATH pairs */ const char *txn_stub; /* for accessing transactions (i.e. txnprops) */ const char *txn_root_stub; /* for accessing TXN/PATH pairs */ + const char *vtxn_stub; /* for accessing transactions (i.e. txnprops) */ + const char *vtxn_root_stub; /* for accessing TXN/PATH pairs */ /*** End HTTP v2 stuff ***/ Index: subversion/libsvn_ra_serf/commit.c =================================================================== --- subversion/libsvn_ra_serf/commit.c (revision 1077861) +++ subversion/libsvn_ra_serf/commit.c (working copy) @@ -1141,6 +1141,21 @@ } static svn_error_t * +setup_post_headers(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool) +{ +#ifdef SVN_SERF_SEND_VTXN_NAME + /* Enable this to exercise the VTXN-NAME code based on a client + supplied transaction name. */ + serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER, + svn_uuid_generate(pool)); +#endif + + return SVN_NO_ERROR; +} + +static svn_error_t * setup_delete_headers(serf_bucket_t *headers, void *baton, apr_pool_t *pool) @@ -1254,6 +1269,18 @@ prc_cc->txn_root_url = svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool); } + + if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0) + { + /* Build out vtxn and vtxn-root URLs using the vtxn name we're + given, and store the whole lot of it in the commit context. */ + prc_cc->txn_name = apr_pstrdup(prc_cc->pool, val); + prc_cc->txn_url = + svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool); + prc_cc->txn_root_url = + svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool); + } + return 0; } @@ -1308,6 +1335,8 @@ handler->body_type = SVN_SKEL_MIME_TYPE; handler->body_delegate = create_txn_post_body; handler->body_delegate_baton = NULL; + handler->header_delegate = setup_post_headers; + handler->header_delegate_baton = NULL; handler->path = ctx->session->me_resource; handler->conn = ctx->session->conns[0]; handler->session = ctx->session; Index: subversion/libsvn_ra_serf/options.c =================================================================== --- subversion/libsvn_ra_serf/options.c (revision 1077861) +++ subversion/libsvn_ra_serf/options.c (working copy) @@ -373,6 +373,14 @@ { orc->session->txn_root_stub = apr_pstrdup(orc->session->pool, val); } + else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0) + { + orc->session->vtxn_stub = apr_pstrdup(orc->session->pool, val); + } + else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0) + { + orc->session->vtxn_root_stub = apr_pstrdup(orc->session->pool, val); + } else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0) { orc->session->uuid = apr_pstrdup(orc->session->pool, val); -- Philip