summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_serf/commit.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_serf/commit.c')
-rw-r--r--subversion/libsvn_ra_serf/commit.c2468
1 files changed, 2468 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c
new file mode 100644
index 0000000..aaf1717
--- /dev/null
+++ b/subversion/libsvn_ra_serf/commit.c
@@ -0,0 +1,2468 @@
+/*
+ * commit.c : entry point for commit RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_base64.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_skel.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/* Baton passed back with the commit editor. */
+typedef struct commit_context_t {
+ /* Pool for our commit. */
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__connection_t *conn;
+
+ apr_hash_t *revprop_table;
+
+ svn_commit_callback2_t callback;
+ void *callback_baton;
+
+ apr_hash_t *lock_tokens;
+ svn_boolean_t keep_locks;
+ apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */
+
+ /* HTTP v2 stuff */
+ const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */
+ const char *txn_root_url; /* commit anchor txn root URL */
+
+ /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
+ const char *activity_url; /* activity base URL... */
+ const char *baseline_url; /* the working-baseline resource */
+ const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
+ const char *vcc_url; /* vcc url */
+
+} commit_context_t;
+
+#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
+
+/* Structure associated with a PROPPATCH request. */
+typedef struct proppatch_context_t {
+ apr_pool_t *pool;
+
+ const char *relpath;
+ const char *path;
+
+ commit_context_t *commit;
+
+ /* Changed and removed properties. */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* Same, for the old value (*old_value_p). */
+ apr_hash_t *previous_changed_props;
+ apr_hash_t *previous_removed_props;
+
+ /* In HTTP v2, this is the file/directory version we think we're changing. */
+ svn_revnum_t base_revision;
+
+} proppatch_context_t;
+
+typedef struct delete_context_t {
+ const char *path;
+
+ svn_revnum_t revision;
+
+ const char *lock_token;
+ apr_hash_t *lock_token_hash;
+ svn_boolean_t keep_locks;
+
+} delete_context_t;
+
+/* Represents a directory. */
+typedef struct dir_context_t {
+ /* Pool for our directory. */
+ apr_pool_t *pool;
+
+ /* The root commit we're in progress for. */
+ commit_context_t *commit;
+
+ /* URL to operate against (used for CHECKOUT and PROPPATCH before
+ HTTP v2, for PROPPATCH in HTTP v2). */
+ const char *url;
+
+ /* How many pending changes we have left in this directory. */
+ unsigned int ref_count;
+
+ /* Is this directory being added? (Otherwise, just opened.) */
+ svn_boolean_t added;
+
+ /* Our parent */
+ struct dir_context_t *parent_dir;
+
+ /* The directory name; if "", we're the 'root' */
+ const char *relpath;
+
+ /* The basename of the directory. "" for the 'root' */
+ const char *name;
+
+ /* The base revision of the dir. */
+ svn_revnum_t base_revision;
+
+ const char *copy_path;
+ svn_revnum_t copy_revision;
+
+ /* Changed and removed properties */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* The checked-out working resource for this directory. May be NULL; if so
+ call checkout_dir() first. */
+ const char *working_url;
+
+} dir_context_t;
+
+/* Represents a file to be committed. */
+typedef struct file_context_t {
+ /* Pool for our file. */
+ apr_pool_t *pool;
+
+ /* The root commit we're in progress for. */
+ commit_context_t *commit;
+
+ /* Is this file being added? (Otherwise, just opened.) */
+ svn_boolean_t added;
+
+ dir_context_t *parent_dir;
+
+ const char *relpath;
+ const char *name;
+
+ /* The checked-out working resource for this file. */
+ const char *working_url;
+
+ /* The base revision of the file. */
+ svn_revnum_t base_revision;
+
+ /* Copy path and revision */
+ const char *copy_path;
+ svn_revnum_t copy_revision;
+
+ /* stream */
+ svn_stream_t *stream;
+
+ /* Temporary file containing the svndiff. */
+ apr_file_t *svndiff;
+
+ /* Our base checksum as reported by the WC. */
+ const char *base_checksum;
+
+ /* Our resulting checksum as reported by the WC. */
+ const char *result_checksum;
+
+ /* Changed and removed properties. */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* URL to PUT the file at. */
+ const char *url;
+
+} file_context_t;
+
+
+/* Setup routines and handlers for various requests we'll invoke. */
+
+static svn_error_t *
+return_response_err(svn_ra_serf__handler_t *handler)
+{
+ svn_error_t *err;
+
+ /* We should have captured SLINE and LOCATION in the HANDLER. */
+ SVN_ERR_ASSERT(handler->handler_pool != NULL);
+
+ /* Ye Olde Fallback Error */
+ err = svn_error_compose_create(
+ handler->server_error != NULL
+ ? handler->server_error->error
+ : SVN_NO_ERROR,
+ svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("%s of '%s': %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason));
+
+ /* Try to return one of the standard errors for 301, 404, etc.,
+ then look for an error embedded in the response. */
+ return svn_error_compose_create(svn_ra_serf__error_on_status(
+ handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_checkout_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ const char *activity_url = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
+
+ SVN_ERR_ASSERT(activity_url != NULL);
+ svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
+ activity_url,
+ strlen(activity_url));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+
+/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
+ given COMMIT_CTX. The resulting working resource will be returned in
+ *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
+ are performed in SCRATCH_POOL.
+
+ ### are these URLs actually repos relpath values? or fspath? or maybe
+ ### the abspath portion of the full URL.
+
+ This function operates synchronously.
+
+ Strictly speaking, we could perform "all" of the CHECKOUT requests
+ when the commit starts, and only block when we need a specific
+ answer. Or, at a minimum, send off these individual requests async
+ and block when we need the answer (eg PUT or PROPPATCH).
+
+ However: the investment to speed this up is not worthwhile, given
+ that CHECKOUT (and the related round trip) is completely obviated
+ in HTTPv2.
+*/
+static svn_error_t *
+checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t handler = { 0 };
+ apr_status_t status;
+ apr_uri_t uri;
+
+ /* HANDLER_POOL is the scratch pool since we don't need to remember
+ anything from the handler. We just want the working resource. */
+ handler.handler_pool = scratch_pool;
+ handler.session = commit_ctx->session;
+ handler.conn = commit_ctx->conn;
+
+ handler.body_delegate = create_checkout_body;
+ handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
+ handler.body_type = "text/xml";
+
+ handler.response_handler = svn_ra_serf__expect_empty_body;
+ handler.response_baton = &handler;
+
+ handler.method = "CHECKOUT";
+ handler.path = node_url;
+
+ SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
+
+ if (handler.sline.code != 201)
+ return svn_error_trace(return_response_err(&handler));
+
+ if (handler.location == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("No Location header received"));
+
+ /* We only want the path portion of the Location header.
+ (code.google.com sometimes returns an 'http:' scheme for an
+ 'https:' transaction ... we'll work around that by stripping the
+ scheme, host, and port here and re-adding the correct ones
+ later. */
+ status = apr_uri_parse(scratch_pool, handler.location, &uri);
+ if (status)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Error parsing Location header value"));
+
+ *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is a wrapper around checkout_node() (which see for
+ documentation) which simply retries the CHECKOUT request when it
+ fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
+ server.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
+ details.
+*/
+static svn_error_t *
+retry_checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int retry_count = 5; /* Magic, arbitrary number. */
+
+ do
+ {
+ svn_error_clear(err);
+
+ err = checkout_node(working_url, commit_ctx, node_url,
+ result_pool, scratch_pool);
+
+ /* There's a small chance of a race condition here if Apache is
+ experiencing heavy commit concurrency or if the network has
+ long latency. It's possible that the value of HEAD changed
+ between the time we fetched the latest baseline and the time
+ we try to CHECKOUT that baseline. If that happens, Apache
+ will throw us a BAD_BASELINE error (deltaV says you can only
+ checkout the latest baseline). We just ignore that specific
+ error and retry a few times, asking for the latest baseline
+ again. */
+ if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
+ return err;
+ }
+ while (err && retry_count--);
+
+ return err;
+}
+
+
+static svn_error_t *
+checkout_dir(dir_context_t *dir,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ dir_context_t *p_dir = dir;
+ const char *checkout_url;
+ const char **working;
+
+ if (dir->working_url)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Is this directory or one of our parent dirs newly added?
+ * If so, we're already implicitly checked out. */
+ while (p_dir)
+ {
+ if (p_dir->added)
+ {
+ /* Implicitly checkout this dir now. */
+ dir->working_url = svn_path_url_add_component2(
+ dir->parent_dir->working_url,
+ dir->name, dir->pool);
+ return SVN_NO_ERROR;
+ }
+ p_dir = p_dir->parent_dir;
+ }
+
+ /* We could be called twice for the root: once to checkout the baseline;
+ * once to checkout the directory itself if we need to do so.
+ * Note: CHECKOUT_URL should live longer than HANDLER.
+ */
+ if (!dir->parent_dir && !dir->commit->baseline_url)
+ {
+ checkout_url = dir->commit->vcc_url;
+ working = &dir->commit->baseline_url;
+ }
+ else
+ {
+ checkout_url = dir->url;
+ working = &dir->working_url;
+ }
+
+ /* Checkout our directory into the activity URL now. */
+ err = retry_checkout_node(working, dir->commit, checkout_url,
+ dir->pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("Directory '%s' is out of date; try updating"),
+ svn_dirent_local_style(dir->relpath, scratch_pool)));
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *CHECKED_IN_URL to the appropriate DAV version url for
+ * RELPATH (relative to the root of SESSION).
+ *
+ * Try to find this version url in three ways:
+ * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
+ * version url from the working copy properties.
+ * Second, if the version url of the parent directory PARENT_VSN_URL is
+ * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
+ * RELPATH.
+ * Else, fetch the version url for the root of SESSION using CONN and
+ * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
+ * with RELPATH.
+ *
+ * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
+ * temporary allocation.
+ */
+static svn_error_t *
+get_version_url(const char **checked_in_url,
+ svn_ra_serf__session_t *session,
+ const char *relpath,
+ svn_revnum_t base_revision,
+ const char *parent_vsn_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *root_checkout;
+
+ if (session->wc_callbacks->get_wc_prop)
+ {
+ const svn_string_t *current_version;
+
+ SVN_ERR(session->wc_callbacks->get_wc_prop(
+ session->wc_callback_baton,
+ relpath,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &current_version, scratch_pool));
+
+ if (current_version)
+ {
+ *checked_in_url =
+ svn_urlpath__canonicalize(current_version->data, result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (parent_vsn_url)
+ {
+ root_checkout = parent_vsn_url;
+ }
+ else
+ {
+ const char *propfind_url;
+ svn_ra_serf__connection_t *conn = session->conns[0];
+
+ if (SVN_IS_VALID_REVNUM(base_revision))
+ {
+ /* mod_dav_svn can't handle the "Label:" header that
+ svn_ra_serf__deliver_props() is going to try to use for
+ this lookup, so we'll do things the hard(er) way, by
+ looking up the version URL from a resource in the
+ baseline collection. */
+ /* ### conn==NULL for session->conns[0]. same as CONN. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
+ NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, base_revision,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ propfind_url = session->session_url.path;
+ }
+
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
+ conn, propfind_url, base_revision,
+ "checked-in",
+ scratch_pool, scratch_pool));
+ if (!root_checkout)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Path '%s' not present"),
+ session->session_url.path);
+
+ root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
+ }
+
+ *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+checkout_file(file_context_t *file,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ dir_context_t *parent_dir = file->parent_dir;
+ const char *checkout_url;
+
+ /* Is one of our parent dirs newly added? If so, we're already
+ * implicitly checked out.
+ */
+ while (parent_dir)
+ {
+ if (parent_dir->added)
+ {
+ /* Implicitly checkout this file now. */
+ file->working_url = svn_path_url_add_component2(
+ parent_dir->working_url,
+ svn_relpath_skip_ancestor(
+ parent_dir->relpath, file->relpath),
+ file->pool);
+ return SVN_NO_ERROR;
+ }
+ parent_dir = parent_dir->parent_dir;
+ }
+
+ SVN_ERR(get_version_url(&checkout_url,
+ file->commit->session,
+ file->relpath, file->base_revision,
+ NULL, scratch_pool, scratch_pool));
+
+ /* Checkout our file into the activity URL now. */
+ err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
+ file->pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("File '%s' is out of date; try updating"),
+ svn_dirent_local_style(file->relpath, scratch_pool)));
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for proppatch_walker() below. */
+static svn_error_t *
+get_encoding_and_cdata(const char **encoding_p,
+ const svn_string_t **encoded_value_p,
+ serf_bucket_alloc_t *alloc,
+ const svn_string_t *value,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (value == NULL)
+ {
+ *encoding_p = NULL;
+ *encoded_value_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* If a property is XML-safe, XML-encode it. Else, base64-encode
+ it. */
+ if (svn_xml_is_xml_safe(value->data, value->len))
+ {
+ svn_stringbuf_t *xml_esc = NULL;
+ svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
+ *encoding_p = NULL;
+ *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
+ }
+ else
+ {
+ *encoding_p = "base64";
+ *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct walker_baton_t {
+ serf_bucket_t *body_bkt;
+ apr_pool_t *body_pool;
+
+ apr_hash_t *previous_changed_props;
+ apr_hash_t *previous_removed_props;
+
+ const char *path;
+
+ /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
+ rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */
+ enum {
+ filter_all_props,
+ filter_props_with_old_value,
+ filter_props_without_old_value
+ } filter;
+
+ /* Is the property being deleted? */
+ svn_boolean_t deleting;
+} walker_baton_t;
+
+/* If we have (recorded in WB) the old value of the property named NS:NAME,
+ * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
+ * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */
+static svn_error_t *
+derive_old_val(svn_boolean_t *have_old_val,
+ const svn_string_t **old_val_p,
+ walker_baton_t *wb,
+ const char *ns,
+ const char *name)
+{
+ *have_old_val = FALSE;
+
+ if (wb->previous_changed_props)
+ {
+ const svn_string_t *val;
+ val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
+ wb->path, ns, name);
+ if (val)
+ {
+ *have_old_val = TRUE;
+ *old_val_p = val;
+ }
+ }
+
+ if (wb->previous_removed_props)
+ {
+ const svn_string_t *val;
+ val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
+ wb->path, ns, name);
+ if (val)
+ {
+ *have_old_val = TRUE;
+ *old_val_p = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+proppatch_walker(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ walker_baton_t *wb = baton;
+ serf_bucket_t *body_bkt = wb->body_bkt;
+ serf_bucket_t *cdata_bkt;
+ serf_bucket_alloc_t *alloc;
+ const char *encoding;
+ svn_boolean_t have_old_val;
+ const svn_string_t *old_val;
+ const svn_string_t *encoded_value;
+ const char *prop_name;
+
+ SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
+
+ /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
+ * representation. */
+ if (wb->filter != filter_all_props)
+ {
+ if (wb->filter == filter_props_with_old_value && ! have_old_val)
+ return SVN_NO_ERROR;
+ if (wb->filter == filter_props_without_old_value && have_old_val)
+ return SVN_NO_ERROR;
+ }
+ if (wb->deleting)
+ val = NULL;
+
+ alloc = body_bkt->allocator;
+
+ SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
+ wb->body_pool, scratch_pool));
+ if (encoded_value)
+ {
+ cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
+ encoded_value->len,
+ alloc);
+ }
+ else
+ {
+ cdata_bkt = NULL;
+ }
+
+ /* Use the namespace prefix instead of adding the xmlns attribute to support
+ property names containing ':' */
+ if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
+ else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
+
+ if (cdata_bkt)
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
+ "V:encoding", encoding,
+ NULL);
+ else
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
+ "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
+ NULL);
+
+ if (have_old_val)
+ {
+ const char *encoding2;
+ const svn_string_t *encoded_value2;
+ serf_bucket_t *cdata_bkt2;
+
+ SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
+ alloc, old_val,
+ wb->body_pool, scratch_pool));
+
+ if (encoded_value2)
+ {
+ cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
+ encoded_value2->len,
+ alloc);
+ }
+ else
+ {
+ cdata_bkt2 = NULL;
+ }
+
+ if (cdata_bkt2)
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE,
+ "V:encoding", encoding2,
+ NULL);
+ else
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE,
+ "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
+ NULL);
+
+ if (cdata_bkt2)
+ serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE);
+ }
+ if (cdata_bkt)
+ serf_bucket_aggregate_append(body_bkt, cdata_bkt);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
+
+ return SVN_NO_ERROR;
+}
+
+/* Possible add the lock-token "If:" precondition header to HEADERS if
+ an examination of COMMIT_CTX and RELPATH indicates that this is the
+ right thing to do.
+
+ Generally speaking, if the client provided a lock token for
+ RELPATH, it's the right thing to do. There is a notable instance
+ where this is not the case, however. If the file at RELPATH was
+ explicitly deleted in this commit already, then mod_dav removed its
+ lock token when it fielded the DELETE request, so we don't want to
+ set the lock precondition again. (See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
+*/
+static svn_error_t *
+maybe_set_lock_token_header(serf_bucket_t *headers,
+ commit_context_t *commit_ctx,
+ const char *relpath,
+ apr_pool_t *pool)
+{
+ const char *token;
+
+ if (! (relpath && commit_ctx->lock_tokens))
+ return SVN_NO_ERROR;
+
+ if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
+ {
+ token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
+ if (token)
+ {
+ const char *token_header;
+ const char *token_uri;
+ apr_uri_t uri = commit_ctx->session->session_url;
+
+ /* Supplying the optional URI affects apache response when
+ the lock is broken, see issue 4369. When present any URI
+ must be absolute (RFC 2518 9.4). */
+ uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
+ pool);
+ token_uri = apr_uri_unparse(pool, &uri, 0);
+
+ token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
+ (char *)NULL);
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_proppatch_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ proppatch_context_t *proppatch = baton;
+
+ if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_psprintf(pool, "%ld",
+ proppatch->base_revision));
+ }
+
+ SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
+ proppatch->relpath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct proppatch_body_baton_t {
+ proppatch_context_t *proppatch;
+
+ /* Content in the body should be allocated here, to live long enough. */
+ apr_pool_t *body_pool;
+};
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_proppatch_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *scratch_pool)
+{
+ struct proppatch_body_baton_t *pbb = baton;
+ proppatch_context_t *ctx = pbb->proppatch;
+ serf_bucket_t *body_bkt;
+ walker_baton_t wb = { 0 };
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
+ "xmlns:D", "DAV:",
+ "xmlns:V", SVN_DAV_PROP_NS_DAV,
+ "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
+ "xmlns:S", SVN_DAV_PROP_NS_SVN,
+ NULL);
+
+ wb.body_bkt = body_bkt;
+ wb.body_pool = pbb->body_pool;
+ wb.previous_changed_props = ctx->previous_changed_props;
+ wb.previous_removed_props = ctx->previous_removed_props;
+ wb.path = ctx->path;
+
+ if (apr_hash_count(ctx->changed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_all_props;
+ wb.deleting = FALSE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
+ }
+
+ if (apr_hash_count(ctx->removed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_props_with_old_value;
+ wb.deleting = TRUE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
+ }
+
+ if (apr_hash_count(ctx->removed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_props_without_old_value;
+ wb.deleting = TRUE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t*
+proppatch_resource(proppatch_context_t *proppatch,
+ commit_context_t *commit,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct proppatch_body_baton_t pbb;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "PROPPATCH";
+ handler->path = proppatch->path;
+ handler->conn = commit->conn;
+ handler->session = commit->session;
+
+ handler->header_delegate = setup_proppatch_headers;
+ handler->header_delegate_baton = proppatch;
+
+ pbb.proppatch = proppatch;
+ pbb.body_pool = pool;
+ handler->body_delegate = create_proppatch_body;
+ handler->body_delegate_baton = &pbb;
+
+ handler->response_handler = svn_ra_serf__handle_multistatus_only;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ if (handler->sline.code != 207
+ || (handler->server_error != NULL
+ && handler->server_error->error != NULL))
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_PROPPATCH_FAILED,
+ return_response_err(handler),
+ _("At least one property change failed; repository"
+ " is unchanged"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_put_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ file_context_t *ctx = baton;
+ apr_off_t offset;
+
+ /* We need to flush the file, make it unbuffered (so that it can be
+ * zero-copied via mmap), and reset the position before attempting to
+ * deliver the file.
+ *
+ * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
+ * and zero-copy the PUT body. However, on older APR versions, we can't
+ * check the buffer status; but serf will fall through and create a file
+ * bucket for us on the buffered svndiff handle.
+ */
+ apr_file_flush(ctx->svndiff);
+#if APR_VERSION_AT_LEAST(1, 3, 0)
+ apr_file_buffer_set(ctx->svndiff, NULL, 0);
+#endif
+ offset = 0;
+ apr_file_seek(ctx->svndiff, APR_SET, &offset);
+
+ *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_empty_put_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_put_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ file_context_t *ctx = baton;
+
+ if (SVN_IS_VALID_REVNUM(ctx->base_revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_psprintf(pool, "%ld", ctx->base_revision));
+ }
+
+ if (ctx->base_checksum)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
+ ctx->base_checksum);
+ }
+
+ if (ctx->result_checksum)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
+ ctx->result_checksum);
+ }
+
+ SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
+ ctx->relpath, pool));
+
+ return APR_SUCCESS;
+}
+
+static svn_error_t *
+setup_copy_file_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ file_context_t *file = baton;
+ apr_uri_t uri;
+ const char *absolute_uri;
+
+ /* The Dest URI must be absolute. Bummer. */
+ uri = file->commit->session->session_url;
+ uri.path = (char*)file->url;
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_setn(headers, "Depth", "0");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_copy_dir_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = baton;
+ apr_uri_t uri;
+ const char *absolute_uri;
+
+ /* The Dest URI must be absolute. Bummer. */
+ uri = dir->commit->session->session_url;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ uri.path = (char *)dir->url;
+ }
+ else
+ {
+ uri.path = (char *)svn_path_url_add_component2(
+ dir->parent_dir->working_url,
+ dir->name, pool);
+ }
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_setn(headers, "Depth", "infinity");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
+
+ /* Implicitly checkout this dir now. */
+ dir->working_url = apr_pstrdup(dir->pool, uri.path);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_delete_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ delete_context_t *ctx = baton;
+
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_ltoa(pool, ctx->revision));
+
+ if (ctx->lock_token_hash)
+ {
+ ctx->lock_token = svn_hash_gets(ctx->lock_token_hash, ctx->path);
+
+ if (ctx->lock_token)
+ {
+ const char *token_header;
+
+ token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
+ ctx->lock_token, ">)", (char *)NULL);
+
+ serf_bucket_headers_set(headers, "If", token_header);
+
+ if (ctx->keep_locks)
+ serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_KEEP_LOCKS);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_delete_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ delete_context_t *ctx = baton;
+ serf_bucket_t *body;
+
+ body = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body, alloc);
+
+ svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
+ body, alloc, pool);
+
+ *body_bkt = body;
+ return SVN_NO_ERROR;
+}
+
+/* Helper function to write the svndiff stream to temporary file. */
+static svn_error_t *
+svndiff_stream_write(void *file_baton,
+ const char *data,
+ apr_size_t *len)
+{
+ file_context_t *ctx = file_baton;
+ apr_status_t status;
+
+ status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
+ if (status)
+ return svn_error_wrap_apr(status, _("Failed writing updated file"));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* POST against 'me' resource handlers. */
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_txn_post_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprops = baton;
+ svn_skel_t *request_skel;
+ svn_stringbuf_t *skel_str;
+
+ request_skel = svn_skel__make_empty_list(pool);
+ if (revprops)
+ {
+ svn_skel_t *proplist_skel;
+
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
+ svn_skel__prepend(proplist_skel, request_skel);
+ svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
+ skel_str = svn_skel__unparse(request_skel, pool);
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
+ }
+ else
+ {
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_header_delegate_t */
+static svn_error_t *
+setup_post_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+#ifdef SVN_DAV_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;
+}
+
+
+/* Handler baton for POST request. */
+typedef struct post_response_ctx_t
+{
+ svn_ra_serf__handler_t *handler;
+ commit_context_t *commit_ctx;
+} post_response_ctx_t;
+
+
+/* This implements serf_bucket_headers_do_callback_fn_t. */
+static int
+post_headers_iterator_callback(void *baton,
+ const char *key,
+ const char *val)
+{
+ post_response_ctx_t *prc = baton;
+ commit_context_t *prc_cc = prc->commit_ctx;
+ svn_ra_serf__session_t *sess = prc_cc->session;
+
+ /* If we provided a UUID to the POST request, we should get back
+ from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
+ expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
+ see both. */
+
+ if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
+ {
+ /* Build out txn and txn-root URLs using the txn name we're
+ given, and store the whole lot of it in the commit context. */
+ prc_cc->txn_url =
+ svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
+ 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_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;
+}
+
+
+/* A custom serf_response_handler_t which is mostly a wrapper around
+ svn_ra_serf__expect_empty_body -- it just notices POST response
+ headers, too.
+
+ Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+post_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ post_response_ctx_t *prc = baton;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
+
+ /* Execute the 'real' response handler to XML-parse the repsonse body. */
+ return svn_ra_serf__expect_empty_body(request, response,
+ prc->handler, scratch_pool);
+}
+
+
+
+/* Commit baton callbacks */
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ commit_context_t *ctx = edit_baton;
+ svn_ra_serf__handler_t *handler;
+ proppatch_context_t *proppatch_ctx;
+ dir_context_t *dir;
+ apr_hash_index_t *hi;
+ const char *proppatch_target = NULL;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
+ {
+ post_response_ctx_t *prc;
+ const char *rel_path;
+ svn_boolean_t post_with_revprops
+ = (NULL != svn_hash_gets(ctx->session->supported_posts,
+ "create-txn-with-props"));
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
+ handler->method = "POST";
+ handler->body_type = SVN_SKEL_MIME_TYPE;
+ handler->body_delegate = create_txn_post_body;
+ handler->body_delegate_baton =
+ post_with_revprops ? ctx->revprop_table : 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;
+
+ prc = apr_pcalloc(ctx->pool, sizeof(*prc));
+ prc->handler = handler;
+ prc->commit_ctx = ctx;
+
+ handler->response_handler = post_response_handler;
+ handler->response_baton = prc;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
+
+ if (handler->sline.code != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ switch (handler->sline.code)
+ {
+ case 403:
+ status = SVN_ERR_RA_DAV_FORBIDDEN;
+ break;
+ case 404:
+ status = SVN_ERR_FS_NOT_FOUND;
+ break;
+ }
+
+ return svn_error_createf(status, NULL,
+ _("%s of '%s': %d %s (%s://%s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason,
+ ctx->session->session_url.scheme,
+ ctx->session->session_url.hostinfo);
+ }
+ if (! (ctx->txn_root_url && ctx->txn_url))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("POST request did not return transaction information"));
+ }
+
+ /* Fixup the txn_root_url to point to the anchor of the commit. */
+ SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
+ ctx->session->session_url.path,
+ ctx->session, NULL, dir_pool));
+ ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
+ rel_path, ctx->pool);
+
+ /* Build our directory baton. */
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->commit = ctx;
+ dir->base_revision = base_revision;
+ dir->relpath = "";
+ dir->name = "";
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+ dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
+
+ /* If we included our revprops in the POST, we need not
+ PROPPATCH them. */
+ proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
+ }
+ else
+ {
+ const char *activity_str = ctx->session->activity_collection_url;
+
+ if (!activity_str)
+ SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
+ ctx->session->conns[0],
+ ctx->pool,
+ ctx->pool));
+
+ /* Cache the result. */
+ if (activity_str)
+ {
+ ctx->session->activity_collection_url =
+ apr_pstrdup(ctx->session->pool, activity_str);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include the "
+ "requested activity-collection-set value"));
+ }
+
+ ctx->activity_url =
+ svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
+ ctx->pool);
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
+ handler->method = "MKACTIVITY";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
+
+ if (handler->sline.code != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ switch (handler->sline.code)
+ {
+ case 403:
+ status = SVN_ERR_RA_DAV_FORBIDDEN;
+ break;
+ case 404:
+ status = SVN_ERR_FS_NOT_FOUND;
+ break;
+ }
+
+ return svn_error_createf(status, NULL,
+ _("%s of '%s': %d %s (%s://%s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason,
+ ctx->session->session_url.scheme,
+ ctx->session->session_url.hostinfo);
+ }
+
+ /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
+ ctx->conn, ctx->pool));
+
+
+ /* Build our directory baton. */
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->commit = ctx;
+ dir->base_revision = base_revision;
+ dir->relpath = "";
+ dir->name = "";
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ SVN_ERR(get_version_url(&dir->url, dir->commit->session,
+ dir->relpath,
+ dir->base_revision, ctx->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
+ ctx->checked_in_url = dir->url;
+
+ /* Checkout our root dir */
+ SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
+
+ proppatch_target = ctx->baseline_url;
+ }
+
+ /* Unless this is NULL -- which means we don't need to PROPPATCH the
+ transaction with our revprops -- then, you know, PROPPATCH the
+ transaction with our revprops. */
+ if (proppatch_target)
+ {
+ proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = dir_pool;
+ proppatch_ctx->commit = ctx;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
+
+ for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ const char *ns;
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props,
+ proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
+ }
+
+ *root_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+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;
+ delete_context_t *delete_ctx;
+ svn_ra_serf__handler_t *handler;
+ const char *delete_target;
+ svn_error_t *err;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
+ path, dir->pool);
+ }
+ 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->path = apr_pstrdup(pool, path);
+ delete_ctx->revision = revision;
+ delete_ctx->lock_token_hash = dir->commit->lock_tokens;
+ delete_ctx->keep_locks = dir->commit->keep_locks;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->session = dir->commit->session;
+ handler->conn = dir->commit->conn;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ handler->header_delegate = setup_delete_headers;
+ handler->header_delegate_baton = delete_ctx;
+
+ handler->method = "DELETE";
+ handler->path = delete_target;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ if (err &&
+ (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
+ err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
+ err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
+ err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
+ {
+ svn_error_clear(err);
+
+ /* An error has been registered on the connection. Reset the thing
+ so that we can use it again. */
+ serf_connection_reset(handler->conn->conn);
+
+ handler->body_delegate = create_delete_body;
+ handler->body_delegate_baton = delete_ctx;
+ handler->body_type = "text/xml";
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+ }
+ else if (err)
+ {
+ return err;
+ }
+
+ /* 204 No Content: item successfully deleted */
+ if (handler->sline.code != 204)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+
+ svn_hash_sets(dir->commit->deleted_entries,
+ apr_pstrdup(dir->commit->pool, path), (void *)1);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ dir_context_t *parent = parent_baton;
+ dir_context_t *dir;
+ svn_ra_serf__handler_t *handler;
+ apr_status_t status;
+ const char *mkcol_target;
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+
+ dir->pool = dir_pool;
+ dir->parent_dir = parent;
+ dir->commit = parent->commit;
+ dir->added = TRUE;
+ dir->base_revision = SVN_INVALID_REVNUM;
+ dir->copy_revision = copyfrom_revision;
+ dir->copy_path = copyfrom_path;
+ dir->relpath = apr_pstrdup(dir->pool, path);
+ dir->name = svn_relpath_basename(dir->relpath, NULL);
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ dir->url = svn_path_url_add_component2(parent->commit->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->checked_in_url,
+ dir->name, dir->pool);
+ mkcol_target = svn_path_url_add_component2(
+ parent->working_url,
+ dir->name, dir->pool);
+ }
+
+ handler = apr_pcalloc(dir->pool, sizeof(*handler));
+ handler->handler_pool = dir->pool;
+ handler->conn = dir->commit->conn;
+ handler->session = dir->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+ if (!dir->copy_path)
+ {
+ handler->method = "MKCOL";
+ handler->path = mkcol_target;
+ }
+ else
+ {
+ apr_uri_t uri;
+ const char *req_url;
+
+ status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"),
+ dir->copy_path);
+ }
+
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ dir->commit->session,
+ NULL /* conn */,
+ 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;
+ }
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
+
+ switch (handler->sline.code)
+ {
+ case 201: /* Created: item was successfully copied */
+ case 204: /* No Content: item successfully replaced an existing target */
+ break;
+
+ case 403:
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"),
+ handler->path);
+ default:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Adding directory failed: %s on %s "
+ "(%d %s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
+
+ *child_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ dir_context_t *parent = parent_baton;
+ dir_context_t *dir;
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+
+ dir->pool = dir_pool;
+
+ dir->parent_dir = parent;
+ dir->commit = parent->commit;
+
+ dir->added = FALSE;
+ dir->base_revision = base_revision;
+ dir->relpath = apr_pstrdup(dir->pool, path);
+ dir->name = svn_relpath_basename(dir->relpath, NULL);
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, dir->pool);
+ }
+ else
+ {
+ SVN_ERR(get_version_url(&dir->url,
+ dir->commit->session,
+ dir->relpath, dir->base_revision,
+ dir->commit->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
+ }
+ *child_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = dir_baton;
+ const char *ns;
+ const char *proppatch_target;
+
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ proppatch_target = dir->url;
+ }
+ else
+ {
+ /* Ensure we have a checked out dir. */
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
+
+ proppatch_target = dir->working_url;
+ }
+
+ name = apr_pstrdup(dir->pool, name);
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ if (value)
+ {
+ value = svn_string_dup(value, dir->pool);
+ svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
+ ns, name, value, dir->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(dir->pool);
+ svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
+ ns, name, value, dir->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = dir_baton;
+
+ /* Huh? We're going to be called before the texts are sent. Ugh.
+ * Therefore, just wave politely at our caller.
+ */
+
+ /* PROPPATCH our prop change and pass it along. */
+ if (apr_hash_count(dir->changed_props) ||
+ apr_hash_count(dir->removed_props))
+ {
+ proppatch_context_t *proppatch_ctx;
+
+ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = pool;
+ proppatch_ctx->commit = dir->commit;
+ proppatch_ctx->relpath = dir->relpath;
+ proppatch_ctx->changed_props = dir->changed_props;
+ proppatch_ctx->removed_props = dir->removed_props;
+ proppatch_ctx->base_revision = dir->base_revision;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ proppatch_ctx->path = dir->url;
+ }
+ else
+ {
+ proppatch_ctx->path = dir->working_url;
+ }
+
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ dir_context_t *dir = parent_baton;
+ file_context_t *new_file;
+ const char *deleted_parent = path;
+
+ new_file = apr_pcalloc(file_pool, sizeof(*new_file));
+ new_file->pool = file_pool;
+
+ dir->ref_count++;
+
+ new_file->parent_dir = dir;
+ new_file->commit = dir->commit;
+ new_file->relpath = apr_pstrdup(new_file->pool, path);
+ new_file->name = svn_relpath_basename(new_file->relpath, NULL);
+ new_file->added = TRUE;
+ new_file->base_revision = SVN_INVALID_REVNUM;
+ new_file->copy_path = copy_path;
+ new_file->copy_revision = copy_revision;
+ new_file->changed_props = apr_hash_make(new_file->pool);
+ new_file->removed_props = apr_hash_make(new_file->pool);
+
+ /* 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))
+ {
+ new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
+ path, new_file->pool);
+ }
+ else
+ {
+ /* Ensure our parent directory has been checked out */
+ SVN_ERR(checkout_dir(dir, new_file->pool /* 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->deleted_entries, deleted_parent))
+ {
+ break;
+ }
+ deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
+ }
+
+ if (! ((dir->added && !dir->copy_path) ||
+ (deleted_parent && deleted_parent[0] != '\0')))
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(new_file->pool, sizeof(*handler));
+ handler->handler_pool = new_file->pool;
+ handler->session = new_file->commit->session;
+ handler->conn = new_file->commit->conn;
+ handler->method = "HEAD";
+ handler->path = svn_path_url_add_component2(
+ dir->commit->session->session_url.path,
+ path, new_file->pool);
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
+
+ if (handler->sline.code != 404)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
+ _("File '%s' already exists"), path);
+ }
+ }
+
+ *file_baton = new_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ dir_context_t *parent = parent_baton;
+ file_context_t *new_file;
+
+ new_file = apr_pcalloc(file_pool, sizeof(*new_file));
+ new_file->pool = file_pool;
+
+ parent->ref_count++;
+
+ new_file->parent_dir = parent;
+ new_file->commit = parent->commit;
+ new_file->relpath = apr_pstrdup(new_file->pool, path);
+ new_file->name = svn_relpath_basename(new_file->relpath, NULL);
+ new_file->added = FALSE;
+ new_file->base_revision = base_revision;
+ new_file->changed_props = apr_hash_make(new_file->pool);
+ new_file->removed_props = apr_hash_make(new_file->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
+ {
+ new_file->url = svn_path_url_add_component2(parent->commit->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 */));
+
+ new_file->url = new_file->working_url;
+ }
+
+ *file_baton = new_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ file_context_t *ctx = file_baton;
+
+ /* Store the stream in a temporary file; 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
+ * 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.
+ *
+ * ctx->pool is the same for all files in the commit that send a
+ * textdelta so this file is explicitly closed in close_file to
+ * avoid too many simultaneously open files.
+ */
+
+ SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ ctx->pool, pool));
+
+ ctx->stream = svn_stream_create(ctx, pool);
+ svn_stream_set_write(ctx->stream, svndiff_stream_write);
+
+ svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+
+ if (base_checksum)
+ ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ file_context_t *file = file_baton;
+ const char *ns;
+
+ name = apr_pstrdup(file->pool, name);
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ if (value)
+ {
+ value = svn_string_dup(value, file->pool);
+ svn_ra_serf__set_prop(file->changed_props, file->url,
+ ns, name, value, file->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(file->pool);
+
+ svn_ra_serf__set_prop(file->removed_props, file->url,
+ ns, name, value, file->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ file_context_t *ctx = file_baton;
+ svn_boolean_t put_empty_file = FALSE;
+ apr_status_t status;
+
+ ctx->result_checksum = text_checksum;
+
+ if (ctx->copy_path)
+ {
+ svn_ra_serf__handler_t *handler;
+ apr_uri_t uri;
+ const char *req_url;
+
+ status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"),
+ ctx->copy_path);
+ }
+
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ctx->commit->session,
+ NULL /* conn */,
+ uri.path, ctx->copy_revision,
+ scratch_pool, scratch_pool));
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "COPY";
+ handler->path = req_url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ handler->header_delegate = setup_copy_file_headers;
+ handler->header_delegate_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 201 && handler->sline.code != 204)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+ }
+
+ /* 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->stream) && ctx->added && (!ctx->copy_path))
+ put_empty_file = TRUE;
+
+ /* If we had a stream of changes, push them to the server... */
+ if (ctx->stream || put_empty_file)
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "PUT";
+ handler->path = ctx->url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ if (put_empty_file)
+ {
+ handler->body_delegate = create_empty_put_body;
+ handler->body_delegate_baton = ctx;
+ handler->body_type = "text/plain";
+ }
+ else
+ {
+ handler->body_delegate = create_put_body;
+ handler->body_delegate_baton = ctx;
+ handler->body_type = SVN_SVNDIFF_MIME_TYPE;
+ }
+
+ handler->header_delegate = setup_put_headers;
+ handler->header_delegate_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 204 && handler->sline.code != 201)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+ }
+
+ if (ctx->svndiff)
+ SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
+
+ /* If we had any prop changes, push them via PROPPATCH. */
+ if (apr_hash_count(ctx->changed_props) ||
+ apr_hash_count(ctx->removed_props))
+ {
+ proppatch_context_t *proppatch;
+
+ proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
+ proppatch->pool = ctx->pool;
+ proppatch->relpath = ctx->relpath;
+ proppatch->path = ctx->url;
+ proppatch->commit = ctx->commit;
+ proppatch->changed_props = ctx->changed_props;
+ proppatch->removed_props = ctx->removed_props;
+ proppatch->base_revision = ctx->base_revision;
+
+ SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ commit_context_t *ctx = edit_baton;
+ const char *merge_target =
+ ctx->activity_url ? ctx->activity_url : ctx->txn_url;
+ const svn_commit_info_t *commit_info;
+ int response_code;
+
+ /* MERGE our activity */
+ SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
+ ctx->session,
+ ctx->session->conns[0],
+ merge_target,
+ ctx->lock_tokens,
+ ctx->keep_locks,
+ pool, pool));
+
+ if (response_code != 200)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("MERGE request failed: returned %d "
+ "(during commit)"),
+ response_code);
+ }
+
+ /* Inform the WC that we did a commit. */
+ if (ctx->callback)
+ SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
+
+ /* If we're using activities, DELETE our completed activity. */
+ if (ctx->activity_url)
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "DELETE";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->conn;
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ SVN_ERR_ASSERT(handler->sline.code == 204);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ commit_context_t *ctx = edit_baton;
+ svn_ra_serf__handler_t *handler;
+
+ /* If an activity or transaction wasn't even created, don't bother
+ trying to delete it. */
+ if (! (ctx->activity_url || ctx->txn_url))
+ return SVN_NO_ERROR;
+
+ /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
+ had a problem. We need to reset it, in order to use it again. */
+ serf_connection_reset(ctx->session->conns[0]->conn);
+
+ /* DELETE our aborted activity */
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "DELETE";
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
+ handler->path = ctx->txn_url;
+ else
+ handler->path = ctx->activity_url;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ /* 204 if deleted,
+ 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
+ 404 if the activity wasn't found. */
+ if (handler->sline.code != 204
+ && handler->sline.code != 403
+ && handler->sline.code != 404
+ )
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
+ const svn_delta_editor_t **ret_editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_delta_editor_t *editor;
+ commit_context_t *ctx;
+ const char *repos_root;
+ const char *base_relpath;
+ svn_boolean_t supports_ephemeral_props;
+
+ ctx = apr_pcalloc(pool, sizeof(*ctx));
+
+ ctx->pool = pool;
+
+ ctx->session = session;
+ ctx->conn = session->conns[0];
+
+ ctx->revprop_table = svn_prop_hash_dup(revprop_table, 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,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ pool));
+ if (supports_ephemeral_props)
+ {
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
+ svn_string_create(SVN_VER_NUMBER, pool));
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
+ svn_string_create(session->useragent, pool));
+ }
+
+ ctx->callback = callback;
+ ctx->callback_baton = callback_baton;
+
+ ctx->lock_tokens = lock_tokens;
+ ctx->keep_locks = keep_locks;
+
+ ctx->deleted_entries = apr_hash_make(ctx->pool);
+
+ editor = svn_delta_default_editor(pool);
+ editor->open_root = open_root;
+ editor->delete_entry = delete_entry;
+ editor->add_directory = add_directory;
+ editor->open_directory = open_directory;
+ editor->change_dir_prop = change_dir_prop;
+ editor->close_directory = close_directory;
+ editor->add_file = add_file;
+ editor->open_file = open_file;
+ editor->apply_textdelta = apply_textdelta;
+ editor->change_file_prop = change_file_prop;
+ editor->close_file = close_file;
+ editor->close_edit = close_edit;
+ editor->abort_edit = abort_edit;
+
+ *ret_editor = editor;
+ *edit_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
+ pool);
+
+ SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
+ *edit_baton, repos_root, base_relpath,
+ session->shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ proppatch_context_t *proppatch_ctx;
+ commit_context_t *commit;
+ const char *proppatch_target;
+ const char *ns;
+ svn_error_t *err;
+
+ if (old_value_p)
+ {
+ svn_boolean_t capable;
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+
+ /* How did you get past the same check in svn_ra_change_rev_prop2()? */
+ SVN_ERR_ASSERT(capable);
+ }
+
+ commit = apr_pcalloc(pool, sizeof(*commit));
+
+ commit->pool = pool;
+
+ commit->session = session;
+ commit->conn = session->conns[0];
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
+ }
+ else
+ {
+ const char *vcc_url;
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
+ commit->conn, pool));
+
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
+ commit->conn, vcc_url, rev,
+ "href",
+ pool, pool));
+ }
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ /* PROPPATCH our log message and pass it along. */
+ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = pool;
+ proppatch_ctx->commit = commit;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ if (old_value_p)
+ {
+ proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
+ }
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
+
+ if (old_value_p && *old_value_p)
+ {
+ svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
+ proppatch_ctx->path,
+ ns, name, *old_value_p, proppatch_ctx->pool);
+ }
+ else if (old_value_p)
+ {
+ svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
+
+ svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
+ proppatch_ctx->path,
+ ns, name, dummy_value, proppatch_ctx->pool);
+ }
+
+ if (value)
+ {
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(proppatch_ctx->pool);
+
+ svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+
+ err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
+ if (err)
+ return
+ svn_error_create
+ (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
+ _("DAV request failed; it's possible that the repository's "
+ "pre-revprop-change hook either failed or is non-existent"));
+
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud