[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH v2] HTTPv2 allow client to control transaction name in protocol

From: Philip Martin <philip.martin_at_wandisco.com>
Date: Fri, 04 Mar 2011 12:29:10 +0000

"C. Michael Pilato" <cmpilato_at_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
Received on 2011-03-04 13:30:00 CET

This is an archived mail posted to the Subversion Dev mailing list.