Greg Stein <gst...@gmail.com> writes:

> Oh, give me a break. "not officially supported" ... This is a public list.
> I'll be nice.
>
> Both of those components have H/2 support now.

I might have misinterpreted the warning banner in httpd documentation [1]
saying that mod_http2 is experimental (or maybe this statement is obsolete):

  [Warning]

  This module is experimental. Its behaviors, directives, and defaults are
  subject to more change from release to release relative to other standard
  modules.

>>  (2) Is making parallel PUTs the proper way to speed up commits?
>
> Absolutely. Typically, the PUT is a delta against a prior revision, it may
> be compressed, and it may be encrypted. ALL of the work to handle those
> aspects can be handled by a different core in the server.
>
> I seem to recall an inspection of our server time, that showed MD5 handling
> was a significant component. By separating PUTs, we can get multiple server
> cores working for us.

I think that local commits are usually fast enough.  But committing over
a high-latency network, e.g., with a transatlantic RTT of 150ms, can be
painfully slow — see below.

>>     As far as I know, squashing everything into a single POST would make
>>     the commit up to 10-20 times faster, depending on the amount of changes.
>
> Pfft.

I attached a dirty patch that does that.  The aim is to measure what we
can expect from this approach.  Please note that the patch is just a proof
of concept, and has many various issues — the wire format, only supporting
short-circuit authz, etc.

Results without and with the patch are:

  Importing 10000 files, 150 ms RTT    1887.582 s → 46.363 s    (40.7x faster)
  Importing 10000 files, 50 ms RTT     660.897 s → 35.267 s     (18.7x faster)
  Committing 25 files, 150 ms RTT      9.198 s → 1.739 s        (5.3x faster)
  Committing 25 files, 50 ms RTT       3.357 s → 0.714 s        (4.7x faster)
  Committing 2 files, 150 ms RTT       1.102 s → 0.787 s        (1.4x faster)
  Committing 2 files, 50 ms RTT        0.589 s → 0.406 s        (1.4x faster)

Sending everything in a single request is probably less appealing than having
parallel PUT requests.  But in the meanwhile, I don't think that parallel
PUTs can provide us with the same performance improvement — at least,
for the high-latency networks.

[1] https://httpd.apache.org/docs/2.4/mod/mod_http2.html


Regards,
Evgeny Kotkov
Index: subversion/include/mod_authz_svn.h
===================================================================
--- subversion/include/mod_authz_svn.h  (revision 1729555)
+++ subversion/include/mod_authz_svn.h  (working copy)
@@ -41,7 +41,7 @@ extern "C" {
 /** Provider name for subrequest bypass */
 #define AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME "mod_authz_svn_subreq_bypass"
 /** Provider version for subrequest bypass */
-#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER "00.00a"
+#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER "01.00a"
 /** Provider to allow mod_dav_svn to bypass the generation of an apache
  * request when checking GET access from "mod_dav_svn/auth.c".
  *
@@ -50,9 +50,11 @@ extern "C" {
  *
  * If the access is allowed returns @c OK or @c HTTP_FORBIDDEN if it is not.
  */
-typedef int (*authz_svn__subreq_bypass_func_t)(request_rec *r,
-                                              const char *repos_path,
-                                              const char *repos_name);
+typedef int
+(*authz_svn__subreq_bypass_func_t)(request_rec *r,
+                                   const char *repos_path,
+                                   const char *repos_name,
+                                   int required_access);
 
 #ifdef __cplusplus
 }
Index: subversion/libsvn_ra_serf/commit.c
===================================================================
--- subversion/libsvn_ra_serf/commit.c  (revision 1729555)
+++ subversion/libsvn_ra_serf/commit.c  (working copy)
@@ -72,6 +72,11 @@ typedef struct commit_context_t {
   const char *vcc_url;           /* vcc url */
 
   int open_batons;               /* Number of open batons */
+
+  svn_ra_serf__request_body_t *body;
+  svn_stream_t *stream;
+  apr_array_header_t *svndiff_sizes;
+  svn_stringbuf_t *header;
 } commit_context_t;
 
 #define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
@@ -170,12 +175,6 @@ typedef struct file_context_t {
   const char *copy_path;
   svn_revnum_t copy_revision;
 
-  /* Stream for collecting the svndiff. */
-  svn_stream_t *stream;
-
-  /* Buffer holding the svndiff (can spill to disk). */
-  svn_ra_serf__request_body_t *svndiff;
-
   /* Our base checksum as reported by the WC. */
   const char *base_checksum;
 
@@ -188,6 +187,8 @@ typedef struct file_context_t {
   /* URL to PUT the file at. */
   const char *url;
 
+  apr_size_t svndiff_size;
+
 } file_context_t;
 
 
@@ -1406,74 +1407,83 @@ create_delete_body(serf_bucket_t **body_bkt,
 }
 
 static svn_error_t *
-delete_entry(const char *path,
-             svn_revnum_t revision,
-             void *parent_baton,
-             apr_pool_t *pool)
+write_bytes(svn_stream_t *stream,
+            const void *buf,
+            apr_size_t nbytes)
 {
-  dir_context_t *dir = parent_baton;
-  delete_context_t *delete_ctx;
-  svn_ra_serf__handler_t *handler;
-  const char *delete_target;
-  svn_error_t *err;
+  if (nbytes > 0)
+    {
+      apr_size_t len = nbytes;
 
-  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
-    {
-      delete_target = svn_path_url_add_component2(
-                                    dir->commit_ctx->txn_root_url,
-                                    path, dir->pool);
+      SVN_ERR(svn_stream_write(stream, buf, &len));
     }
-  else
-    {
-      /* Ensure our directory has been checked out */
-      SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
-      delete_target = svn_path_url_add_component2(dir->working_url,
-                                                  svn_relpath_basename(path,
-                                                                       NULL),
-                                                  pool);
-    }
 
-  /* DELETE our entry */
-  delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
-  delete_ctx->relpath = apr_pstrdup(pool, path);
-  delete_ctx->revision = revision;
-  delete_ctx->commit_ctx = dir->commit_ctx;
+  return SVN_NO_ERROR;
+}
 
-  handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
+static svn_error_t *
+write_bool(svn_stream_t *stream, svn_boolean_t value)
+{
+  SVN_ERR(write_bytes(stream, value ? "\1" : "\0", 1));
 
-  handler->response_handler = svn_ra_serf__expect_empty_body;
-  handler->response_baton = handler;
+  return SVN_NO_ERROR;
+}
 
-  handler->header_delegate = setup_delete_headers;
-  handler->header_delegate_baton = delete_ctx;
+static svn_error_t *
+write_int32(svn_stream_t *stream, apr_int32_t value)
+{
+  SVN_ERR(write_bytes(stream, &value, sizeof(value)));
 
-  handler->method = "DELETE";
-  handler->path = delete_target;
+  return SVN_NO_ERROR;
+}
 
-  err = svn_ra_serf__context_run_one(handler, pool);
-  if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED
-      && handler->sline.code == 400)
+static svn_error_t *
+write_uint32(svn_stream_t *stream, apr_uint32_t value)
+{
+  SVN_ERR(write_bytes(stream, &value, sizeof(value)));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_uint64(svn_stream_t *stream, apr_uint64_t value)
+{
+  SVN_ERR(write_bytes(stream, &value, sizeof(value)));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_string(svn_stream_t *stream, const char *value)
+{
+  if (value)
     {
-      svn_error_clear(err);
+      apr_size_t len = strlen(value);
 
-      /* Try again with non-standard body to overcome Apache Httpd
-         header limit */
-      delete_ctx->non_recursive_if = TRUE;
-      handler->body_type = "text/xml";
-      handler->body_delegate = create_delete_body;
-      handler->body_delegate_baton = delete_ctx;
-
-      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+      SVN_ERR_ASSERT(len < APR_UINT32_MAX);
+      SVN_ERR(write_uint32(stream, (apr_uint32_t) len));
+      SVN_ERR(write_bytes(stream, value, len));
     }
   else
-    SVN_ERR(err);
+    {
+      SVN_ERR(write_uint32(stream, APR_UINT32_MAX));
+    }
 
-  /* 204 No Content: item successfully deleted */
-  if (handler->sline.code != 204)
-    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
+  return SVN_NO_ERROR;
+}
 
-  svn_hash_sets(dir->commit_ctx->deleted_entries,
-                apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
+static svn_error_t *
+delete_entry(const char *path,
+             svn_revnum_t revision,
+             void *parent_baton,
+             apr_pool_t *pool)
+{
+  dir_context_t *dir = parent_baton;
+  svn_stream_t *stream = dir->commit_ctx->stream;
+  
+  SVN_ERR(write_string(stream, "delete"));
+  SVN_ERR(write_string(stream, path));
+  SVN_ERR(write_int32(stream, revision));
 
   return SVN_NO_ERROR;
 }
@@ -1488,9 +1498,7 @@ add_directory(const char *path,
 {
   dir_context_t *parent = parent_baton;
   dir_context_t *dir;
-  svn_ra_serf__handler_t *handler;
-  apr_status_t status;
-  const char *mkcol_target;
+  svn_stream_t *stream = parent->commit_ctx->stream;
 
   dir = apr_pcalloc(dir_pool, sizeof(*dir));
 
@@ -1507,67 +1515,24 @@ add_directory(const char *path,
 
   dir->commit_ctx->open_batons++;
 
-  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
+  if (copyfrom_path)
     {
-      dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
-                                             path, dir->pool);
-      mkcol_target = dir->url;
-    }
-  else
-    {
-      /* Ensure our parent is checked out. */
-      SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
-
-      dir->url = 
svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
-                                             dir->name, dir->pool);
-      mkcol_target = svn_path_url_add_component2(
-                               parent->working_url,
-                               dir->name, dir->pool);
-    }
-
-  handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
-
-  handler->response_handler = svn_ra_serf__expect_empty_body;
-  handler->response_baton = handler;
-  if (!dir->copy_path)
-    {
-      handler->method = "MKCOL";
-      handler->path = mkcol_target;
-
-      handler->header_delegate = setup_add_dir_common_headers;
-      handler->header_delegate_baton = dir;
-    }
-  else
-    {
       apr_uri_t uri;
-      const char *req_url;
+      apr_status_t status;
 
-      status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
+      status = apr_uri_parse(dir_pool, copyfrom_path, &uri);
       if (status)
-        {
-          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
-                                   _("Unable to parse URL '%s'"),
-                                   dir->copy_path);
-        }
+        return svn_ra_serf__wrap_err(status, NULL);
 
-      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
-                                          dir->commit_ctx->session,
-                                          uri.path, dir->copy_revision,
-                                          dir_pool, dir_pool));
-
-      handler->method = "COPY";
-      handler->path = req_url;
-
-      handler->header_delegate = setup_copy_dir_headers;
-      handler->header_delegate_baton = dir;
+      copyfrom_path =
+        svn_urlpath__skip_ancestor(dir->commit_ctx->session->repos_root.path,
+                                   uri.path);
     }
-  /* We have the same problem as with DELETE here: if there are too many
-     locks, the request fails. But in this case there is no way to retry
-     with a non-standard request. #### How to fix? */
-  SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
 
-  if (handler->sline.code != 201)
-    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
+  SVN_ERR(write_string(stream, "add-dir"));
+  SVN_ERR(write_string(stream, path));
+  SVN_ERR(write_string(stream, copyfrom_path));
+  SVN_ERR(write_int32(stream, copyfrom_revision));
 
   *child_baton = dir;
 
@@ -1583,6 +1548,7 @@ open_directory(const char *path,
 {
   dir_context_t *parent = parent_baton;
   dir_context_t *dir;
+  svn_stream_t *stream = parent->commit_ctx->stream;
 
   dir = apr_pcalloc(dir_pool, sizeof(*dir));
 
@@ -1599,19 +1565,10 @@ open_directory(const char *path,
 
   dir->commit_ctx->open_batons++;
 
-  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
-    {
-      dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
-                                             path, dir->pool);
-    }
-  else
-    {
-      SVN_ERR(get_version_url(&dir->url,
-                              dir->commit_ctx->session,
-                              dir->relpath, dir->base_revision,
-                              dir->commit_ctx->checked_in_url,
-                              dir->pool, dir->pool /* scratch_pool */));
-    }
+  SVN_ERR(write_string(stream, "open-dir"));
+  SVN_ERR(write_string(stream, path));
+  SVN_ERR(write_int32(stream, base_revision));
+
   *child_baton = dir;
 
   return SVN_NO_ERROR;
@@ -1624,21 +1581,22 @@ change_dir_prop(void *dir_baton,
                 apr_pool_t *scratch_pool)
 {
   dir_context_t *dir = dir_baton;
-  svn_prop_t *prop;
+  svn_stream_t *stream = dir->commit_ctx->stream;
 
-  if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
+  SVN_ERR(write_string(stream, "change-dir-prop"));
+  SVN_ERR(write_string(stream, dir->relpath));
+  SVN_ERR(write_string(stream, name));
+  if (value)
     {
-      /* Ensure we have a checked out dir. */
-      SVN_ERR(checkout_dir(dir, scratch_pool));
+      SVN_ERR(write_bool(stream, TRUE));
+      SVN_ERR(write_uint64(stream, value->len));
+      SVN_ERR(write_bytes(stream, value->data, value->len));
     }
+  else
+    {
+      SVN_ERR(write_bool(stream, FALSE));
+    }
 
-  prop = apr_palloc(dir->pool, sizeof(*prop));
-
-  prop->name = apr_pstrdup(dir->pool, name);
-  prop->value = svn_string_dup(value, dir->pool);
-
-  svn_hash_sets(dir->prop_changes, prop->name, prop);
-
   return SVN_NO_ERROR;
 }
 
@@ -1652,31 +1610,6 @@ close_directory(void *dir_baton,
    * Therefore, just wave politely at our caller.
    */
 
-  /* PROPPATCH our prop change and pass it along.  */
-  if (apr_hash_count(dir->prop_changes))
-    {
-      proppatch_context_t *proppatch_ctx;
-
-      proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
-      proppatch_ctx->pool = pool;
-      proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
-      proppatch_ctx->relpath = dir->relpath;
-      proppatch_ctx->prop_changes = dir->prop_changes;
-      proppatch_ctx->base_revision = dir->base_revision;
-
-      if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
-        {
-          proppatch_ctx->path = dir->url;
-        }
-      else
-        {
-          proppatch_ctx->path = dir->working_url;
-        }
-
-      SVN_ERR(proppatch_resource(dir->commit_ctx->session,
-                                 proppatch_ctx, dir->pool));
-    }
-
   dir->commit_ctx->open_batons--;
 
   return SVN_NO_ERROR;
@@ -1692,8 +1625,7 @@ add_file(const char *path,
 {
   dir_context_t *dir = parent_baton;
   file_context_t *new_file;
-  const char *deleted_parent = path;
-  apr_pool_t *scratch_pool = svn_pool_create(file_pool);
+  svn_stream_t *stream = dir->commit_ctx->stream;
 
   new_file = apr_pcalloc(file_pool, sizeof(*new_file));
   new_file->pool = file_pool;
@@ -1710,98 +1642,25 @@ add_file(const char *path,
 
   dir->commit_ctx->open_batons++;
 
-  /* Ensure that the file doesn't exist by doing a HEAD on the
-     resource.  If we're using HTTP v2, we'll just look into the
-     transaction root tree for this thing.  */
-  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
-    {
-      new_file->url = 
svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
-                                                  path, new_file->pool);
-    }
-  else
-    {
-      /* Ensure our parent directory has been checked out */
-      SVN_ERR(checkout_dir(dir, scratch_pool));
-
-      new_file->url =
-        svn_path_url_add_component2(dir->working_url,
-                                    new_file->name, new_file->pool);
-    }
-
-  while (deleted_parent && deleted_parent[0] != '\0')
-    {
-      if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
-        {
-          break;
-        }
-      deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
-    }
-
   if (copy_path)
     {
-      svn_ra_serf__handler_t *handler;
       apr_uri_t uri;
-      const char *req_url;
       apr_status_t status;
 
-      /* Create the copy directly as cheap 'does exist/out of date'
-         check. We update the copy (if needed) from close_file() */
-
-      status = apr_uri_parse(scratch_pool, copy_path, &uri);
+      status = apr_uri_parse(file_pool, copy_path, &uri);
       if (status)
         return svn_ra_serf__wrap_err(status, NULL);
 
-      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
-                                          dir->commit_ctx->session,
-                                          uri.path, copy_revision,
-                                          scratch_pool, scratch_pool));
-
-      handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
-                                            scratch_pool);
-      handler->method = "COPY";
-      handler->path = req_url;
-
-      handler->response_handler = svn_ra_serf__expect_empty_body;
-      handler->response_baton = handler;
-
-      handler->header_delegate = setup_copy_file_headers;
-      handler->header_delegate_baton = new_file;
-
-      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
-
-      if (handler->sline.code != 201)
-        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
+      copy_path =
+        svn_urlpath__skip_ancestor(dir->commit_ctx->session->repos_root.path,
+                                   uri.path);
     }
-  else if (! ((dir->added && !dir->copy_path) ||
-           (deleted_parent && deleted_parent[0] != '\0')))
-    {
-      svn_ra_serf__handler_t *handler;
-      svn_error_t *err;
 
-      handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
-                                            scratch_pool);
-      handler->method = "HEAD";
-      handler->path = svn_path_url_add_component2(
-                                        
dir->commit_ctx->session->session_url.path,
-                                        path, scratch_pool);
-      handler->response_handler = svn_ra_serf__expect_empty_body;
-      handler->response_baton = handler;
-      handler->no_dav_headers = TRUE; /* Read only operation outside txn */
+  SVN_ERR(write_string(stream, "add-file"));
+  SVN_ERR(write_string(stream, path));
+  SVN_ERR(write_string(stream, copy_path));
+  SVN_ERR(write_int32(stream, copy_revision));
 
-      err = svn_ra_serf__context_run_one(handler, scratch_pool);
-
-      if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
-        {
-          svn_error_clear(err); /* Great. We can create a new file! */
-        }
-      else if (err)
-        return svn_error_trace(err);
-      else
-        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
-                                 _("File '%s' already exists"), path);
-    }
-
-  svn_pool_destroy(scratch_pool);
   *file_baton = new_file;
 
   return SVN_NO_ERROR;
@@ -1816,6 +1675,7 @@ open_file(const char *path,
 {
   dir_context_t *parent = parent_baton;
   file_context_t *new_file;
+  svn_stream_t *stream = parent->commit_ctx->stream;
 
   new_file = apr_pcalloc(file_pool, sizeof(*new_file));
   new_file->pool = file_pool;
@@ -1830,25 +1690,35 @@ open_file(const char *path,
 
   parent->commit_ctx->open_batons++;
 
-  if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
-    {
-      new_file->url = 
svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
-                                                  path, new_file->pool);
-    }
-  else
-    {
-      /* CHECKOUT the file into our activity. */
-      SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
+  SVN_ERR(write_string(stream, "open-file"));
+  SVN_ERR(write_string(stream, path));
+  SVN_ERR(write_int32(stream, base_revision));
 
-      new_file->url = new_file->working_url;
-    }
-
   *file_baton = new_file;
 
   return SVN_NO_ERROR;
 }
 
+typedef struct counting_stream_baton_t
+{
+  svn_stream_t *stream;
+  apr_size_t *count;
+} counting_stream_baton_t;
+
 static svn_error_t *
+write_fn(void *baton,
+         const char *data,
+         apr_size_t *len)
+{
+  counting_stream_baton_t *b = baton;
+
+  SVN_ERR(svn_stream_write(b->stream, data, len));
+  *b->count += *len;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
 apply_textdelta(void *file_baton,
                 const char *base_checksum,
                 apr_pool_t *pool,
@@ -1858,30 +1728,9 @@ apply_textdelta(void *file_baton,
   file_context_t *ctx = file_baton;
   int svndiff_version;
   int compression_level;
+  counting_stream_baton_t *baton;
+  svn_stream_t *stream;
 
-  /* Construct a holder for the request body; we'll give it to serf when we
-   * close this file.
-   *
-   * TODO: There should be a way we can stream the request body instead of
-   * possibly writing to a temporary file (ugh). A special svn stream serf
-   * bucket that returns EAGAIN until we receive the done call?  But, when
-   * would we run through the serf context?  Grr.
-   *
-   * BH: If you wait to a specific event... why not use that event to
-   *     trigger the operation?
-   *     Having a request (body) bucket return EAGAIN until done stalls
-   *     the entire HTTP pipeline after writing the first part of the
-   *     request. It is not like we can interrupt some part of a request
-   *     and continue later. Or somebody else must use tempfiles and
-   *     always assume that clients work this bad... as it only knows
-   *     for sure after the request is completely available.
-   */
-
-  ctx->svndiff =
-    svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
-                                     ctx->pool);
-  ctx->stream = svn_ra_serf__request_body_get_stream(ctx->svndiff);
-
   if (ctx->commit_ctx->session->supports_svndiff1 &&
       ctx->commit_ctx->session->using_compression)
     {
@@ -1905,14 +1754,22 @@ apply_textdelta(void *file_baton,
       compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
     }
 
-  /* Disown the stream; we'll close it explicitly in close_file(). */
-  svn_txdelta_to_svndiff3(handler, handler_baton,
-                          svn_stream_disown(ctx->stream, pool),
+  baton = apr_pcalloc(pool, sizeof(*baton));
+  baton->stream = svn_stream_disown(ctx->commit_ctx->stream, pool);
+  baton->count = &ctx->svndiff_size;
+  stream = svn_stream_create(baton, pool);
+  svn_stream_set_write(stream, write_fn);
+
+  svn_txdelta_to_svndiff3(handler, handler_baton, stream,
                           svndiff_version, compression_level, pool);
 
   if (base_checksum)
     ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
 
+  SVN_ERR(write_string(ctx->commit_ctx->stream, "apply-textdelta"));
+  SVN_ERR(write_string(ctx->commit_ctx->stream, ctx->relpath));
+  SVN_ERR(write_string(ctx->commit_ctx->stream, base_checksum));
+
   return SVN_NO_ERROR;
 }
 
@@ -1923,15 +1780,22 @@ change_file_prop(void *file_baton,
                  apr_pool_t *pool)
 {
   file_context_t *file = file_baton;
-  svn_prop_t *prop;
+  svn_stream_t *stream = file->commit_ctx->stream;
 
-  prop = apr_palloc(file->pool, sizeof(*prop));
+  SVN_ERR(write_string(stream, "change-file-prop"));
+  SVN_ERR(write_string(stream, file->relpath));
+  SVN_ERR(write_string(stream, name));
+  if (value)
+    {
+      SVN_ERR(write_bool(stream, TRUE));
+      SVN_ERR(write_uint64(stream, value->len));
+      SVN_ERR(write_bytes(stream, value->data, value->len));
+    }
+  else
+    {
+      SVN_ERR(write_bool(stream, FALSE));
+    }
 
-  prop->name = apr_pstrdup(file->pool, name);
-  prop->value = svn_string_dup(value, file->pool);
-
-  svn_hash_sets(file->prop_changes, prop->name, prop);
-
   return SVN_NO_ERROR;
 }
 
@@ -1941,84 +1805,47 @@ close_file(void *file_baton,
            apr_pool_t *scratch_pool)
 {
   file_context_t *ctx = file_baton;
-  svn_boolean_t put_empty_file = FALSE;
+  svn_stream_t *stream = ctx->commit_ctx->stream;
 
   ctx->result_checksum = text_checksum;
 
-  /* If we got no stream of changes, but this is an added-without-history
-   * file, make a note that we'll be PUTting a zero-byte file to the server.
-   */
-  if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
-    put_empty_file = TRUE;
+  ctx->commit_ctx->open_batons--;
 
-  /* If we had a stream of changes, push them to the server... */
-  if (ctx->svndiff || put_empty_file)
-    {
-      svn_ra_serf__handler_t *handler;
-      int expected_result;
+  SVN_ERR(write_string(stream, "close-file"));
+  SVN_ERR(write_string(stream, ctx->relpath));
+  SVN_ERR(write_string(stream, text_checksum));
 
-      handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
-                                            scratch_pool);
+  if (ctx->svndiff_size)
+   APR_ARRAY_PUSH(ctx->commit_ctx->svndiff_sizes,
+                  apr_size_t) = ctx->svndiff_size;
 
-      handler->method = "PUT";
-      handler->path = ctx->url;
+  return SVN_NO_ERROR;
+}
 
-      handler->response_handler = svn_ra_serf__expect_empty_body;
-      handler->response_baton = handler;
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_replay_txn_body(serf_bucket_t **body_bkt,
+                       void *baton,
+                       serf_bucket_alloc_t *alloc,
+                       apr_pool_t *pool /* request pool */,
+                       apr_pool_t *scratch_pool)
+{
+  commit_context_t *ctx = baton;
+  serf_bucket_t *body = serf_bucket_aggregate_create(alloc);
+  serf_bucket_t *tmp;
+  svn_ra_serf__request_body_delegate_t delegate;
+  void *delegate_baton;
 
-      if (put_empty_file)
-        {
-          handler->body_delegate = create_empty_put_body;
-          handler->body_delegate_baton = ctx;
-          handler->body_type = "text/plain";
-        }
-      else
-        {
-          SVN_ERR(svn_stream_close(ctx->stream));
+  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(ctx->header->data,
+                                      ctx->header->len, alloc);
+  serf_bucket_aggregate_append(body, tmp);
 
-          svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
-                                                 &handler->body_delegate_baton,
-                                                 ctx->svndiff);
-          handler->body_type = SVN_SVNDIFF_MIME_TYPE;
-        }
+  svn_ra_serf__request_body_get_delegate(&delegate, &delegate_baton,
+                                         ctx->body);
+  SVN_ERR(delegate(&tmp, delegate_baton, alloc, pool, scratch_pool));
+  serf_bucket_aggregate_append(body, tmp);
 
-      handler->header_delegate = setup_put_headers;
-      handler->header_delegate_baton = ctx;
-
-      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
-
-      if (ctx->added && ! ctx->copy_path)
-        expected_result = 201; /* Created */
-      else
-        expected_result = 204; /* Updated */
-
-      if (handler->sline.code != expected_result)
-        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
-    }
-
-  /* Don't keep open file handles longer than necessary. */
-  if (ctx->svndiff)
-    SVN_ERR(svn_ra_serf__request_body_cleanup(ctx->svndiff, scratch_pool));
-
-  /* If we had any prop changes, push them via PROPPATCH. */
-  if (apr_hash_count(ctx->prop_changes))
-    {
-      proppatch_context_t *proppatch;
-
-      proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
-      proppatch->pool = scratch_pool;
-      proppatch->relpath = ctx->relpath;
-      proppatch->path = ctx->url;
-      proppatch->commit_ctx = ctx->commit_ctx;
-      proppatch->prop_changes = ctx->prop_changes;
-      proppatch->base_revision = ctx->base_revision;
-
-      SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
-                                 proppatch, scratch_pool));
-    }
-
-  ctx->commit_ctx->open_batons--;
-
+  *body_bkt = body;
   return SVN_NO_ERROR;
 }
 
@@ -2031,6 +1858,9 @@ close_edit(void *edit_baton,
     ctx->activity_url ? ctx->activity_url : ctx->txn_url;
   const svn_commit_info_t *commit_info;
   svn_error_t *err = NULL;
+  svn_ra_serf__handler_t *handler;
+  svn_stream_t *header_stream;
+  const char *relpath;
 
   if (ctx->open_batons > 0)
     return svn_error_create(
@@ -2037,6 +1867,67 @@ close_edit(void *edit_baton,
               SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
               _("Closing editor with directories or files open"));
 
+  SVN_ERR(write_string(ctx->stream, "close-edit"));
+  SVN_ERR(svn_stream_close(ctx->stream));
+
+  SVN_ERR(svn_ra_serf__get_relative_path(&relpath,
+                                         ctx->session->session_url.path,
+                                         ctx->session, pool));
+
+  header_stream = svn_stream_from_stringbuf(ctx->header, pool);
+  SVN_ERR(write_string(header_stream, "header"));
+  SVN_ERR(write_string(header_stream, relpath));
+  SVN_ERR(write_int32(header_stream, ctx->svndiff_sizes->nelts));
+
+  if (ctx->svndiff_sizes->nelts > 0)
+    {
+      int i;
+
+      for (i = 0; i < ctx->svndiff_sizes->nelts; i++)
+        {
+          apr_size_t size = APR_ARRAY_IDX(ctx->svndiff_sizes, i, apr_size_t);
+
+          SVN_ERR(write_uint64(header_stream, size));
+        }
+    }
+
+  if (ctx->lock_tokens)
+    {
+      apr_hash_index_t *hi;
+
+      SVN_ERR(write_uint32(header_stream, apr_hash_count(ctx->lock_tokens)));
+
+      for (hi = apr_hash_first(pool, ctx->lock_tokens); hi;
+           hi = apr_hash_next(hi))
+        {
+          const char *relpath = apr_hash_this_key(hi);
+          const char *token = apr_hash_this_val(hi);
+
+          SVN_ERR(write_string(header_stream, relpath));
+          SVN_ERR(write_string(header_stream, token));
+        }
+    }
+  else
+    {
+      SVN_ERR(write_uint32(header_stream, 0));
+    }
+
+  SVN_ERR(svn_stream_close(header_stream));
+
+  /* POST everything -- the skel and every svndiff. */
+  handler = svn_ra_serf__create_handler(ctx->session, pool);
+  handler->method = "POST";
+  handler->path = ctx->txn_url;
+  handler->response_handler = svn_ra_serf__expect_empty_body;
+  handler->response_baton = handler;
+  handler->body_delegate = create_replay_txn_body;
+  handler->body_delegate_baton = ctx;
+
+  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+  if (handler->sline.code != 200)
+    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
+
   /* MERGE our activity */
   SVN_ERR(svn_ra_serf__run_merge(&commit_info,
                                  ctx->session,
@@ -2154,6 +2045,16 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *r
 
   ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
 
+  ctx->body =
+    svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
+                                     pool);
+
+  ctx->stream = svn_ra_serf__request_body_get_stream(ctx->body);
+
+  ctx->svndiff_sizes = apr_array_make(pool, 0, sizeof(apr_size_t));
+
+  ctx->header = svn_stringbuf_create_ensure(64, pool);
+
   /* If the server supports ephemeral properties, add some carrying
      interesting version information. */
   SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
Index: subversion/mod_authz_svn/mod_authz_svn.c
===================================================================
--- subversion/mod_authz_svn/mod_authz_svn.c    (revision 1729555)
+++ subversion/mod_authz_svn/mod_authz_svn.c    (working copy)
@@ -783,6 +783,7 @@ static int
 subreq_bypass2(request_rec *r,
                const char *repos_path,
                const char *repos_name,
+               svn_repos_authz_access_t required_access,
                apr_pool_t *scratch_pool)
 {
   svn_error_t *svn_err = NULL;
@@ -816,7 +817,7 @@ subreq_bypass2(request_rec *r,
       svn_err = svn_repos_authz_check_access(access_conf, repos_name,
                                              repos_path,
                                              username_to_authorize,
-                                             svn_authz_none|svn_authz_read,
+                                             required_access,
                                              &authz_access_granted,
                                              scratch_pool);
       if (svn_err)
@@ -846,13 +847,15 @@ subreq_bypass2(request_rec *r,
 static int
 subreq_bypass(request_rec *r,
               const char *repos_path,
-              const char *repos_name)
+              const char *repos_name,
+              svn_repos_authz_access_t required_access)
 {
   int status;
   apr_pool_t *scratch_pool;
 
   scratch_pool = svn_pool_create(r->pool);
-  status = subreq_bypass2(r, repos_path, repos_name, scratch_pool);
+  status = subreq_bypass2(r, repos_path, repos_name, required_access,
+                          scratch_pool);
   svn_pool_destroy(scratch_pool);
 
   return status;
Index: subversion/mod_dav_svn/authz.c
===================================================================
--- subversion/mod_dav_svn/authz.c      (revision 1729555)
+++ subversion/mod_dav_svn/authz.c      (working copy)
@@ -67,7 +67,8 @@ dav_svn__allow_read(request_rec *r,
   allow_read_bypass = dav_svn__get_pathauthz_bypass(r);
   if (allow_read_bypass != NULL)
     {
-      if (allow_read_bypass(r, path, repos->repo_basename) == OK)
+      if (allow_read_bypass(r, path, repos->repo_basename,
+                            svn_authz_none|svn_authz_read) == OK)
         return TRUE;
       else
         return FALSE;
@@ -243,3 +244,44 @@ dav_svn__allow_read_resource(const dav_resource *r
   return dav_svn__allow_read(resource->info->r, resource->info->repos,
                              resource->info->repos_path, rev, pool);
 }
+
+static svn_error_t *
+authz_callback(svn_repos_authz_access_t required,
+               svn_boolean_t *allowed,
+               svn_fs_root_t *root,
+               const char *path,
+               void *baton,
+               apr_pool_t *pool)
+{
+  dav_svn__authz_callback_baton *acb = baton;
+  authz_svn__subreq_bypass_func_t bypass_func = NULL;
+
+  if (path && path[0] != '/')
+    path = apr_pstrcat(pool, "/", path, SVN_VA_NULL);
+
+  bypass_func = dav_svn__get_pathauthz_bypass(acb->r);
+  if (bypass_func != NULL)
+    {
+      if (bypass_func(acb->r, path, acb->repos->repo_basename,
+                      required) == OK)
+        *allowed = TRUE;
+      else
+        *allowed = FALSE;
+    }
+  else
+    {
+      /* ### Not implemented. */
+      *allowed = TRUE;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+svn_repos_authz_callback_t
+dav_svn__authz_callback(dav_svn__authz_callback_baton *baton)
+{
+  if (! dav_svn__get_pathauthz_flag(baton->r))
+    return NULL;
+
+  return authz_callback;
+}
Index: subversion/mod_dav_svn/dav_svn.h
===================================================================
--- subversion/mod_dav_svn/dav_svn.h    (revision 1729555)
+++ subversion/mod_dav_svn/dav_svn.h    (working copy)
@@ -749,6 +749,15 @@ typedef struct dav_svn__authz_read_baton
 } dav_svn__authz_read_baton;
 
 
+typedef struct dav_svn__authz_callback_baton
+{
+  request_rec *r;
+
+  const dav_svn_repos *repos;
+
+} dav_svn__authz_callback_baton;
+
+
 /* Return TRUE iff the current user (as determined by Apache's
    authentication system) has permission to read PATH in REPOS at REV
    (where an invalid REV means "HEAD").  This will invoke any authz
@@ -795,7 +804,10 @@ dav_svn__allow_list_repos(request_rec *r,
 svn_repos_authz_func_t
 dav_svn__authz_read_func(dav_svn__authz_read_baton *baton);
 
+svn_repos_authz_callback_t
+dav_svn__authz_callback(dav_svn__authz_callback_baton *baton);
 
+
 /*** util.c ***/
 
 /* A wrapper around mod_dav's dav_new_error_tag, mod_dav_svn uses this
Index: subversion/mod_dav_svn/repos.c
===================================================================
--- subversion/mod_dav_svn/repos.c      (revision 1729555)
+++ subversion/mod_dav_svn/repos.c      (working copy)
@@ -4705,7 +4705,416 @@ handle_err(request_rec *r, dav_error *err)
   return err->status;
 }
 
+static svn_error_t *
+read_bytes(ap_filter_t *filter,
+           apr_bucket_brigade *bb,
+           void *buf,
+           apr_size_t nbytes)
+{
+  apr_size_t total_len = 0;
+  apr_status_t status;
+  apr_bucket *bucket;
+  svn_boolean_t seen_eos = FALSE;
 
+  while (!seen_eos && total_len < nbytes)
+    {
+      status = ap_get_brigade(filter, bb, AP_MODE_READBYTES,
+                              APR_BLOCK_READ, nbytes - total_len);
+      if (status)
+        return svn_error_wrap_apr(status, NULL);
+
+      for (bucket = APR_BRIGADE_FIRST(bb);
+           bucket != APR_BRIGADE_SENTINEL(bb);
+           bucket = APR_BUCKET_NEXT(bucket))
+        {
+          const char *data;
+          apr_size_t len;
+
+          if (APR_BUCKET_IS_EOS(bucket))
+            {
+              seen_eos = TRUE;
+              break;
+          }
+
+          if (APR_BUCKET_IS_METADATA(bucket))
+            continue;
+
+          status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
+          if (status)
+            return svn_error_wrap_apr(status, NULL);
+
+          memcpy((char *) buf + total_len, data, len);
+          total_len += len;
+        }
+
+      status = apr_brigade_cleanup(bb);
+      if (status)
+        return svn_error_wrap_apr(status, NULL);
+    }
+
+  if (total_len < nbytes)
+    return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, NULL);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_bool(svn_boolean_t *value_p,
+          ap_filter_t *filter,
+          apr_bucket_brigade *bb)
+{
+  char c;
+
+  SVN_ERR(read_bytes(filter, bb, &c, 1));
+
+  if (c == 0)
+    *value_p = FALSE;
+  else if (c == 1)
+    *value_p = TRUE;
+  else
+    return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_int32(apr_int32_t *value_p,
+           ap_filter_t *filter,
+           apr_bucket_brigade *bb)
+{
+  SVN_ERR(read_bytes(filter, bb, value_p, sizeof(apr_int32_t)));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_uint32(apr_uint32_t *value_p,
+            ap_filter_t *filter,
+            apr_bucket_brigade *bb)
+{
+  SVN_ERR(read_bytes(filter, bb, value_p, sizeof(apr_uint32_t)));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_uint64(apr_uint64_t *value_p,
+            ap_filter_t *filter,
+            apr_bucket_brigade *bb)
+{
+  SVN_ERR(read_bytes(filter, bb, value_p, sizeof(apr_uint64_t)));
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+read_string(const char **value_p,
+            ap_filter_t *filter,
+            apr_bucket_brigade *bb,
+            apr_pool_t *pool)
+{
+  char *value;
+  apr_uint32_t len;
+
+  SVN_ERR(read_uint32(&len, filter, bb));
+
+  if (len == APR_UINT32_MAX)
+    value = NULL;
+  else
+    {
+      /* ### Need a reasonable limit here. */
+      value = apr_palloc(pool, len + 1);
+      SVN_ERR(read_bytes(filter, bb, value, len));
+      value[len] = '\0';
+    }
+
+  *value_p = value;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+replay_txn(const dav_resource *resource,
+           request_rec *r,
+           apr_bucket_brigade *bb,
+           apr_pool_t *pool)
+{
+  const svn_delta_editor_t *editor;
+  void *edit_baton;
+  apr_hash_t *batons;
+  void *root_baton;
+  apr_array_header_t *svndiff_sizes;
+  int svndiff_index;
+  dav_svn__authz_callback_baton acb = {0};
+  apr_pool_t *iterpool;
+
+  editor = NULL;
+  edit_baton = NULL;
+  batons = apr_hash_make(pool);
+  root_baton = NULL;
+  svndiff_sizes = NULL;
+  svndiff_index = 0;
+
+  acb.r = resource->info->r;
+  acb.repos = resource->info->repos;
+
+  iterpool = svn_pool_create(pool);
+  while (1)
+    {
+      const char *s;
+
+      svn_pool_clear(iterpool);
+      SVN_ERR(read_string(&s, r->input_filters, bb, iterpool));
+
+      if (strcmp(s, "header") == 0)
+        {
+          const char *base_path;
+          apr_int32_t base_rev;
+          apr_int32_t len;
+          apr_uint32_t ulen;
+
+          SVN_ERR(read_string(&base_path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_int32(&len, r->input_filters, bb));
+          svndiff_sizes = apr_array_make(pool, len, sizeof(size_t));
+          if (len > 0)
+            {
+              int i;
+
+              for (i = 0; i < len; i++)
+                {
+                  apr_uint64_t size;
+
+                  SVN_ERR(read_uint64(&size, r->input_filters, bb));
+                  APR_ARRAY_IDX(svndiff_sizes, i, size_t) = (size_t) size;
+                }
+            }
+
+          SVN_ERR(read_uint32(&ulen, r->input_filters, bb));
+          if (ulen > 0)
+            {
+              svn_fs_access_t *access_ctx;
+              apr_uint32_t i;
+
+              SVN_ERR(svn_fs_get_access(&access_ctx,
+                                        resource->info->repos->fs));
+
+              if (!access_ctx)
+                return svn_error_create(SVN_ERR_FS_LOCK_OWNER_MISMATCH,
+                                        NULL, NULL);
+
+              for (i = 0; i < ulen; i++)
+                {
+                  const char *path;
+                  const char *token;
+
+                  SVN_ERR(read_string(&path, r->input_filters, bb, pool));
+                  SVN_ERR(read_string(&token, r->input_filters, bb, pool));
+                  SVN_ERR(svn_fs_access_add_lock_token2(access_ctx,
+                                                        path, token));
+                }
+            }
+
+          SVN_ERR(svn_repos_get_commit_editor5(
+                                &editor, &edit_baton,
+                                resource->info->repos->repos,
+                                resource->info->root.txn, "",
+                                base_path, apr_hash_make(pool),
+                                NULL, NULL,
+                                dav_svn__authz_callback(&acb), &acb,
+                                pool));
+
+          base_rev = svn_fs_txn_base_revision(resource->info->root.txn);
+          SVN_ERR(editor->open_root(edit_baton, base_rev, pool, &root_baton));
+          svn_hash_sets(batons, "", root_baton);
+        }
+      else if (strcmp(s, "add-file") == 0)
+        {
+          const char *path;
+          const char *copyfrom_path;
+          apr_int32_t copyfrom_rev;
+          void *file_baton;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, pool));
+          SVN_ERR(read_string(&copyfrom_path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_int32(&copyfrom_rev, r->input_filters, bb));
+
+          SVN_ERR(editor->add_file(path, root_baton, copyfrom_path,
+                                   copyfrom_rev, pool, &file_baton));
+          svn_hash_sets(batons, path, file_baton);
+        }
+      else if (strcmp(s, "add-dir") == 0)
+        {
+          const char *path;
+          const char *copyfrom_path;
+          apr_int32_t copyfrom_rev;
+          void *dir_baton;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, pool));
+          SVN_ERR(read_string(&copyfrom_path, r->input_filters, bb,
+                              iterpool));
+          SVN_ERR(read_int32(&copyfrom_rev, r->input_filters, bb));
+
+          SVN_ERR(editor->add_directory(path, root_baton, copyfrom_path,
+                                        copyfrom_rev, pool, &dir_baton));
+          svn_hash_sets(batons, path, dir_baton);
+        }
+      else if (strcmp(s, "open-file") == 0)
+        {
+          const char *path;
+          apr_int32_t base_rev;
+          const char *parent_path;
+          void *file_baton;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, pool));
+          SVN_ERR(read_int32(&base_rev, r->input_filters, bb));
+
+          parent_path = svn_relpath_dirname(path, iterpool);
+          SVN_ERR(editor->open_file(path,
+                                    svn_hash_gets(batons, parent_path),
+                                    base_rev, pool,
+                                    &file_baton));
+          svn_hash_sets(batons, path, file_baton);
+        }
+      else if (strcmp(s, "open-dir") == 0)
+        {
+          const char *path;
+          apr_int32_t base_rev;
+          const char *parent_path;
+          void *dir_baton;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, pool));
+          SVN_ERR(read_int32(&base_rev, r->input_filters, bb));
+
+          parent_path = svn_relpath_dirname(path, iterpool);
+          SVN_ERR(editor->open_directory(path,
+                                         svn_hash_gets(batons, parent_path),
+                                         base_rev, pool,
+                                         &dir_baton));
+          svn_hash_sets(batons, path, dir_baton);
+        }
+      else if (strcmp(s, "delete") == 0)
+        {
+          const char *path;
+          apr_int32_t rev;
+          const char *parent_path;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_int32(&rev, r->input_filters, bb));
+
+          parent_path = svn_relpath_dirname(path, iterpool);
+          SVN_ERR(editor->delete_entry(path, rev,
+                                       svn_hash_gets(batons, parent_path),
+                                       iterpool));
+        }
+      else if (strcmp(s, "apply-textdelta") == 0)
+        {
+          const char *path;
+          const char *base_checksum;
+          svn_txdelta_window_handler_t wh;
+          void *wb;
+          svn_stream_t *svndiff_parser;
+          apr_size_t svndiff_len;
+          apr_size_t remaining;
+          char buf[APR_BUCKET_BUFF_SIZE];
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_string(&base_checksum, r->input_filters, bb,
+                              iterpool));
+
+          SVN_ERR(editor->apply_textdelta(svn_hash_gets(batons, path),
+                                          base_checksum, iterpool, &wh, &wb));
+          svndiff_parser = svn_txdelta_parse_svndiff(wh, wb, TRUE, iterpool);
+          svndiff_len = APR_ARRAY_IDX(svndiff_sizes, svndiff_index, size_t);
+          remaining = svndiff_len;
+
+          while (remaining > 0)
+            {
+              apr_size_t n = MIN(remaining, sizeof(buf));
+
+              SVN_ERR(read_bytes(r->input_filters, bb, buf, n));
+              SVN_ERR(svn_stream_write(svndiff_parser, buf, &n));
+              remaining -= n;
+            }
+
+          SVN_ERR(svn_stream_close(svndiff_parser));
+          svndiff_index++;
+        }
+      else if (strcmp(s, "change-file-prop") == 0)
+        {
+          const char *path;
+          const char *propname;
+          svn_boolean_t has_propval;
+          svn_string_t *propval;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_string(&propname, r->input_filters, bb, iterpool));
+          SVN_ERR(read_bool(&has_propval, r->input_filters, bb));
+          if (has_propval)
+            {
+              apr_uint64_t len;
+              char *val;
+
+              SVN_ERR(read_uint64(&len, r->input_filters, bb));
+              val = apr_palloc(iterpool, len);
+              SVN_ERR(read_bytes(r->input_filters, bb, val, len));
+              propval = svn_string_ncreate(val, len, iterpool);
+            }
+          else
+            {
+              propval = NULL;
+            }
+
+          SVN_ERR(editor->change_file_prop(svn_hash_gets(batons, path),
+                                           propname, propval, iterpool));
+        }
+      else if (strcmp(s, "change-dir-prop") == 0)
+        {
+          const char *path;
+          const char *propname;
+          svn_boolean_t has_propval;
+          svn_string_t *propval;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_string(&propname, r->input_filters, bb, iterpool));
+          SVN_ERR(read_bool(&has_propval, r->input_filters, bb));
+          if (has_propval)
+            {
+              apr_uint64_t len;
+              char *val;
+
+              SVN_ERR(read_uint64(&len, r->input_filters, bb));
+              val = apr_palloc(iterpool, len);
+              SVN_ERR(read_bytes(r->input_filters, bb, val, len));
+              propval = svn_string_ncreate(val, len, iterpool);
+            }
+          else
+            {
+              propval = NULL;
+            }
+
+          SVN_ERR(editor->change_dir_prop(svn_hash_gets(batons, path),
+                                          propname, propval, iterpool));
+        }
+      else if (strcmp(s, "close-file") == 0)
+        {
+          const char *path;
+          const char *result_checksum;
+
+          SVN_ERR(read_string(&path, r->input_filters, bb, iterpool));
+          SVN_ERR(read_string(&result_checksum, r->input_filters, bb,
+                              iterpool));
+          SVN_ERR(editor->close_file(svn_hash_gets(batons, path),
+                                     result_checksum, iterpool));
+        }
+      else if (strcmp(s, "close-edit") == 0)
+        break;
+      else
+        return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL);
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
 int dav_svn__method_post(request_rec *r)
 {
   dav_resource *resource;
@@ -4712,25 +5121,56 @@ int dav_svn__method_post(request_rec *r)
   dav_error *derr;
   const char *content_type;
 
-  /* We only allow POSTs against the "me resource" right now. */
   derr = get_resource(r, dav_svn__get_root_dir(r),
                       "ignored", 0, &resource);
   if (derr != NULL)
     return derr->status;
-  if (resource->info->restype != DAV_SVN_RESTYPE_ME)
-    return HTTP_BAD_REQUEST;
 
-  /* Pass skel-type POST request handling off to a dispatcher; any
-     other type of request is considered bogus. */
-  content_type = apr_table_get(r->headers_in, "content-type");
-  if (content_type && (strcmp(content_type, SVN_SKEL_MIME_TYPE) == 0))
+  if (resource->info->restype == DAV_SVN_RESTYPE_ME)
     {
-      derr = handle_post_request(r, resource, r->output_filters);
+      /* Pass skel-type POST request handling off to a dispatcher; any
+         other type of request is considered bogus. */
+      content_type = apr_table_get(r->headers_in, "content-type");
+      if (content_type && (strcmp(content_type, SVN_SKEL_MIME_TYPE) == 0))
+        {
+          derr = handle_post_request(r, resource, r->output_filters);
+        }
+      else
+        {
+          derr = dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0,
+                                    "Unsupported POST request type.");
+        }
     }
+  else if (resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)
+    {
+      apr_bucket_brigade *bb;
+      svn_error_t *err;
+
+      bb = apr_brigade_create(resource->pool, r->connection->bucket_alloc);
+      err = replay_txn(resource, r, bb, resource->pool);
+      apr_brigade_destroy(bb);
+
+      if (err && err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE)
+        {
+          derr = dav_svn__convert_err(err, HTTP_CONFLICT,
+                                      "Attempting to modify out-of-date "
+                                      "resource.", resource->pool);
+        }
+      else if (err && (err->apr_err == SVN_ERR_STREAM_MALFORMED_DATA ||
+                       err->apr_err == SVN_ERR_STREAM_UNEXPECTED_EOF))
+        {
+          derr = dav_svn__convert_err(err, HTTP_BAD_REQUEST, NULL,
+                                      resource->pool);
+        }
+      else if (err)
+        {
+          derr = dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, NULL,
+                                      resource->pool);
+        }
+    }
   else
     {
-      derr = dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0,
-                                "Unsupported POST request type.");
+      return HTTP_BAD_REQUEST;
     }
 
   /* If something went wrong above, we'll generate a response back to

Reply via email to