2 * commit.c : entry point for commit RA functions for ra_serf
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include "svn_pools.h"
32 #include "svn_config.h"
33 #include "svn_delta.h"
34 #include "svn_base64.h"
35 #include "svn_dirent_uri.h"
37 #include "svn_props.h"
39 #include "svn_private_config.h"
40 #include "private/svn_dep_compat.h"
41 #include "private/svn_fspath.h"
42 #include "private/svn_skel.h"
45 #include "../libsvn_ra/ra_loader.h"
48 /* Baton passed back with the commit editor. */
49 typedef struct commit_context_t {
50 /* Pool for our commit. */
53 svn_ra_serf__session_t *session;
55 apr_hash_t *revprop_table;
57 svn_commit_callback2_t callback;
60 apr_hash_t *lock_tokens;
61 svn_boolean_t keep_locks;
62 apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */
65 const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */
66 const char *txn_root_url; /* commit anchor txn root URL */
68 /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
69 const char *activity_url; /* activity base URL... */
70 const char *baseline_url; /* the working-baseline resource */
71 const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
72 const char *vcc_url; /* vcc url */
74 int open_batons; /* Number of open batons */
77 #define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
79 /* Structure associated with a PROPPATCH request. */
80 typedef struct proppatch_context_t {
86 commit_context_t *commit_ctx;
88 /* Changed properties. const char * -> svn_prop_t * */
89 apr_hash_t *prop_changes;
91 /* Same, for the old value, or NULL. */
92 apr_hash_t *old_props;
94 /* In HTTP v2, this is the file/directory version we think we're changing. */
95 svn_revnum_t base_revision;
97 } proppatch_context_t;
99 typedef struct delete_context_t {
102 svn_revnum_t revision;
104 commit_context_t *commit_ctx;
106 svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */
109 /* Represents a directory. */
110 typedef struct dir_context_t {
111 /* Pool for our directory. */
114 /* The root commit we're in progress for. */
115 commit_context_t *commit_ctx;
117 /* URL to operate against (used for CHECKOUT and PROPPATCH before
118 HTTP v2, for PROPPATCH in HTTP v2). */
121 /* Is this directory being added? (Otherwise, just opened.) */
125 struct dir_context_t *parent_dir;
127 /* The directory name; if "", we're the 'root' */
130 /* The basename of the directory. "" for the 'root' */
133 /* The base revision of the dir. */
134 svn_revnum_t base_revision;
136 const char *copy_path;
137 svn_revnum_t copy_revision;
139 /* Changed properties (const char * -> svn_prop_t *) */
140 apr_hash_t *prop_changes;
142 /* The checked-out working resource for this directory. May be NULL; if so
143 call checkout_dir() first. */
144 const char *working_url;
147 /* Represents a file to be committed. */
148 typedef struct file_context_t {
149 /* Pool for our file. */
152 /* The root commit we're in progress for. */
153 commit_context_t *commit_ctx;
155 /* Is this file being added? (Otherwise, just opened.) */
158 dir_context_t *parent_dir;
163 /* The checked-out working resource for this file. */
164 const char *working_url;
166 /* The base revision of the file. */
167 svn_revnum_t base_revision;
169 /* Copy path and revision */
170 const char *copy_path;
171 svn_revnum_t copy_revision;
173 /* Stream for collecting the svndiff. */
174 svn_stream_t *stream;
176 /* Buffer holding the svndiff (can spill to disk). */
177 svn_ra_serf__request_body_t *svndiff;
179 /* Did we send the svndiff in apply_textdelta_stream()? */
180 svn_boolean_t svndiff_sent;
182 /* Our base checksum as reported by the WC. */
183 const char *base_checksum;
185 /* Our resulting checksum as reported by the WC. */
186 const char *result_checksum;
188 /* Our resulting checksum as reported by the server. */
189 svn_checksum_t *remote_result_checksum;
191 /* Changed properties (const char * -> svn_prop_t *) */
192 apr_hash_t *prop_changes;
194 /* URL to PUT the file at. */
200 /* Setup routines and handlers for various requests we'll invoke. */
202 /* Implements svn_ra_serf__request_body_delegate_t */
204 create_checkout_body(serf_bucket_t **bkt,
206 serf_bucket_alloc_t *alloc,
207 apr_pool_t *pool /* request pool */,
208 apr_pool_t *scratch_pool)
210 const char *activity_url = baton;
211 serf_bucket_t *body_bkt;
213 body_bkt = serf_bucket_aggregate_create(alloc);
215 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
216 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
219 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set",
221 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href",
224 SVN_ERR_ASSERT(activity_url != NULL);
225 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
227 strlen(activity_url));
229 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
230 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
231 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
232 "D:apply-to-version", SVN_VA_NULL);
233 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
240 /* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
241 given COMMIT_CTX. The resulting working resource will be returned in
242 *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
243 are performed in SCRATCH_POOL.
245 ### are these URLs actually repos relpath values? or fspath? or maybe
246 ### the abspath portion of the full URL.
248 This function operates synchronously.
250 Strictly speaking, we could perform "all" of the CHECKOUT requests
251 when the commit starts, and only block when we need a specific
252 answer. Or, at a minimum, send off these individual requests async
253 and block when we need the answer (eg PUT or PROPPATCH).
255 However: the investment to speed this up is not worthwhile, given
256 that CHECKOUT (and the related round trip) is completely obviated
260 checkout_node(const char **working_url,
261 const commit_context_t *commit_ctx,
262 const char *node_url,
263 apr_pool_t *result_pool,
264 apr_pool_t *scratch_pool)
266 svn_ra_serf__handler_t *handler;
270 /* HANDLER_POOL is the scratch pool since we don't need to remember
271 anything from the handler. We just want the working resource. */
272 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
274 handler->body_delegate = create_checkout_body;
275 handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
276 handler->body_type = "text/xml";
278 handler->response_handler = svn_ra_serf__expect_empty_body;
279 handler->response_baton = handler;
281 handler->method = "CHECKOUT";
282 handler->path = node_url;
284 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
286 if (handler->sline.code != 201)
287 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
289 if (handler->location == NULL)
290 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
291 _("No Location header received"));
293 /* We only want the path portion of the Location header.
294 (code.google.com sometimes returns an 'http:' scheme for an
295 'https:' transaction ... we'll work around that by stripping the
296 scheme, host, and port here and re-adding the correct ones
298 status = apr_uri_parse(scratch_pool, handler->location, &uri);
300 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
301 _("Error parsing Location header value"));
303 *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
309 /* This is a wrapper around checkout_node() (which see for
310 documentation) which simply retries the CHECKOUT request when it
311 fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
314 See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
318 retry_checkout_node(const char **working_url,
319 const commit_context_t *commit_ctx,
320 const char *node_url,
321 apr_pool_t *result_pool,
322 apr_pool_t *scratch_pool)
324 svn_error_t *err = SVN_NO_ERROR;
325 int retry_count = 5; /* Magic, arbitrary number. */
329 svn_error_clear(err);
331 err = checkout_node(working_url, commit_ctx, node_url,
332 result_pool, scratch_pool);
334 /* There's a small chance of a race condition here if Apache is
335 experiencing heavy commit concurrency or if the network has
336 long latency. It's possible that the value of HEAD changed
337 between the time we fetched the latest baseline and the time
338 we try to CHECKOUT that baseline. If that happens, Apache
339 will throw us a BAD_BASELINE error (deltaV says you can only
340 checkout the latest baseline). We just ignore that specific
341 error and retry a few times, asking for the latest baseline
343 if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
344 return svn_error_trace(err);
346 while (err && retry_count--);
348 return svn_error_trace(err);
353 checkout_dir(dir_context_t *dir,
354 apr_pool_t *scratch_pool)
356 dir_context_t *c_dir = dir;
357 const char *checkout_url;
358 const char **working;
360 if (dir->working_url)
365 /* Is this directory or one of our parent dirs newly added?
366 * If so, we're already implicitly checked out. */
371 /* Calculate the working_url by skipping the shared ancestor between
372 * the c_dir_parent->relpath and dir->relpath. This is safe since an
373 * add is guaranteed to have a parent that is checked out. */
374 dir_context_t *c_dir_parent = c_dir->parent_dir;
375 const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath,
378 /* Implicitly checkout this dir now. */
379 SVN_ERR_ASSERT(c_dir_parent->working_url);
380 dir->working_url = svn_path_url_add_component2(
381 c_dir_parent->working_url,
385 c_dir = c_dir->parent_dir;
388 /* We could be called twice for the root: once to checkout the baseline;
389 * once to checkout the directory itself if we need to do so.
390 * Note: CHECKOUT_URL should live longer than HANDLER.
392 if (!dir->parent_dir && !dir->commit_ctx->baseline_url)
394 checkout_url = dir->commit_ctx->vcc_url;
395 working = &dir->commit_ctx->baseline_url;
399 checkout_url = dir->url;
400 working = &dir->working_url;
403 /* Checkout our directory into the activity URL now. */
404 return svn_error_trace(retry_checkout_node(working, dir->commit_ctx,
406 dir->pool, scratch_pool));
410 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
411 * RELPATH (relative to the root of SESSION).
413 * Try to find this version url in three ways:
414 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
415 * version url from the working copy properties.
416 * Second, if the version url of the parent directory PARENT_VSN_URL is
417 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
419 * Else, fetch the version url for the root of SESSION using CONN and
420 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
423 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
424 * temporary allocation.
427 get_version_url(const char **checked_in_url,
428 svn_ra_serf__session_t *session,
430 svn_revnum_t base_revision,
431 const char *parent_vsn_url,
432 apr_pool_t *result_pool,
433 apr_pool_t *scratch_pool)
435 const char *root_checkout;
437 if (session->wc_callbacks->get_wc_prop)
439 const svn_string_t *current_version;
441 SVN_ERR(session->wc_callbacks->get_wc_prop(
442 session->wc_callback_baton,
444 SVN_RA_SERF__WC_CHECKED_IN_URL,
445 ¤t_version, scratch_pool));
450 svn_urlpath__canonicalize(current_version->data, result_pool);
457 root_checkout = parent_vsn_url;
461 const char *propfind_url;
463 if (SVN_IS_VALID_REVNUM(base_revision))
465 /* mod_dav_svn can't handle the "Label:" header that
466 svn_ra_serf__deliver_props() is going to try to use for
467 this lookup, so we'll do things the hard(er) way, by
468 looking up the version URL from a resource in the
469 baseline collection. */
470 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
471 NULL /* latest_revnum */,
473 NULL /* url */, base_revision,
474 scratch_pool, scratch_pool));
478 propfind_url = session->session_url.path;
481 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session,
482 propfind_url, base_revision,
484 scratch_pool, scratch_pool));
486 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
487 _("Path '%s' not present"),
488 session->session_url.path);
490 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
493 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
500 checkout_file(file_context_t *file,
501 apr_pool_t *scratch_pool)
503 dir_context_t *parent_dir = file->parent_dir;
504 const char *checkout_url;
506 /* Is one of our parent dirs newly added? If so, we're already
507 * implicitly checked out.
511 if (parent_dir->added)
513 /* Implicitly checkout this file now. */
514 SVN_ERR_ASSERT(parent_dir->working_url);
515 file->working_url = svn_path_url_add_component2(
516 parent_dir->working_url,
517 svn_relpath_skip_ancestor(
518 parent_dir->relpath, file->relpath),
522 parent_dir = parent_dir->parent_dir;
525 SVN_ERR(get_version_url(&checkout_url,
526 file->commit_ctx->session,
527 file->relpath, file->base_revision,
528 NULL, scratch_pool, scratch_pool));
530 /* Checkout our file into the activity URL now. */
531 return svn_error_trace(retry_checkout_node(&file->working_url,
532 file->commit_ctx, checkout_url,
533 file->pool, scratch_pool));
536 /* Helper function for proppatch_walker() below. */
538 get_encoding_and_cdata(const char **encoding_p,
539 const svn_string_t **encoded_value_p,
540 serf_bucket_alloc_t *alloc,
541 const svn_string_t *value,
542 apr_pool_t *result_pool,
543 apr_pool_t *scratch_pool)
548 *encoded_value_p = NULL;
552 /* If a property is XML-safe, XML-encode it. Else, base64-encode
554 if (svn_xml_is_xml_safe(value->data, value->len))
556 svn_stringbuf_t *xml_esc = NULL;
557 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
559 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
563 *encoding_p = "base64";
564 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
570 /* Helper for create_proppatch_body. Writes per property xml to body */
572 write_prop_xml(const proppatch_context_t *proppatch,
573 serf_bucket_t *body_bkt,
574 serf_bucket_alloc_t *alloc,
575 const svn_prop_t *prop,
576 apr_pool_t *result_pool,
577 apr_pool_t *scratch_pool)
579 serf_bucket_t *cdata_bkt;
580 const char *encoding;
581 const svn_string_t *encoded_value;
582 const char *prop_name;
583 const svn_prop_t *old_prop;
585 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value,
586 result_pool, scratch_pool));
589 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
598 /* Use the namespace prefix instead of adding the xmlns attribute to support
599 property names containing ':' */
600 if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
602 prop_name = apr_pstrcat(result_pool,
603 "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1,
608 prop_name = apr_pstrcat(result_pool,
614 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
615 "V:encoding", encoding,
618 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
619 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
622 old_prop = proppatch->old_props
623 ? svn_hash_gets(proppatch->old_props, prop->name)
627 const char *encoding2;
628 const svn_string_t *encoded_value2;
629 serf_bucket_t *cdata_bkt2;
631 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
632 alloc, old_prop->value,
633 result_pool, scratch_pool));
637 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
647 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
648 "V:" SVN_DAV__OLD_VALUE,
649 "V:encoding", encoding2,
652 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
653 "V:" SVN_DAV__OLD_VALUE,
654 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
658 serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
660 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
661 "V:" SVN_DAV__OLD_VALUE);
664 serf_bucket_aggregate_append(body_bkt, cdata_bkt);
665 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
670 /* Possible add the lock-token "If:" precondition header to HEADERS if
671 an examination of COMMIT_CTX and RELPATH indicates that this is the
674 Generally speaking, if the client provided a lock token for
675 RELPATH, it's the right thing to do. There is a notable instance
676 where this is not the case, however. If the file at RELPATH was
677 explicitly deleted in this commit already, then mod_dav removed its
678 lock token when it fielded the DELETE request, so we don't want to
679 set the lock precondition again. (See
680 http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
683 maybe_set_lock_token_header(serf_bucket_t *headers,
684 commit_context_t *commit_ctx,
690 if (! commit_ctx->lock_tokens)
693 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
695 token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
698 const char *token_header;
699 const char *token_uri;
700 apr_uri_t uri = commit_ctx->session->session_url;
702 /* Supplying the optional URI affects apache response when
703 the lock is broken, see issue 4369. When present any URI
704 must be absolute (RFC 2518 9.4). */
705 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
707 token_uri = apr_uri_unparse(pool, &uri, 0);
709 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
711 serf_bucket_headers_set(headers, "If", token_header);
719 setup_proppatch_headers(serf_bucket_t *headers,
721 apr_pool_t *pool /* request pool */,
722 apr_pool_t *scratch_pool)
724 proppatch_context_t *proppatch = baton;
726 if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
728 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
729 apr_psprintf(pool, "%ld",
730 proppatch->base_revision));
733 if (proppatch->relpath && proppatch->commit_ctx)
734 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx,
735 proppatch->relpath, pool));
741 /* Implements svn_ra_serf__request_body_delegate_t */
743 create_proppatch_body(serf_bucket_t **bkt,
745 serf_bucket_alloc_t *alloc,
746 apr_pool_t *pool /* request pool */,
747 apr_pool_t *scratch_pool)
749 proppatch_context_t *ctx = baton;
750 serf_bucket_t *body_bkt;
751 svn_boolean_t opened = FALSE;
752 apr_hash_index_t *hi;
754 body_bkt = serf_bucket_aggregate_create(alloc);
756 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
757 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
759 "xmlns:V", SVN_DAV_PROP_NS_DAV,
760 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
761 "xmlns:S", SVN_DAV_PROP_NS_SVN,
764 /* First we write property SETs */
765 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
767 hi = apr_hash_next(hi))
769 svn_prop_t *prop = apr_hash_this_val(hi);
772 || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
777 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set",
779 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
783 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
784 pool, scratch_pool));
790 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
791 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
794 /* And then property REMOVEs */
797 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
799 hi = apr_hash_next(hi))
801 svn_prop_t *prop = apr_hash_this_val(hi);
804 && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
809 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove",
811 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
815 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
816 pool, scratch_pool));
822 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
823 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
826 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
833 proppatch_resource(svn_ra_serf__session_t *session,
834 proppatch_context_t *proppatch,
837 svn_ra_serf__handler_t *handler;
840 handler = svn_ra_serf__create_handler(session, pool);
842 handler->method = "PROPPATCH";
843 handler->path = proppatch->path;
845 handler->header_delegate = setup_proppatch_headers;
846 handler->header_delegate_baton = proppatch;
848 handler->body_delegate = create_proppatch_body;
849 handler->body_delegate_baton = proppatch;
850 handler->body_type = "text/xml";
852 handler->response_handler = svn_ra_serf__handle_multistatus_only;
853 handler->response_baton = handler;
855 err = svn_ra_serf__context_run_one(handler, pool);
857 if (!err && handler->sline.code != 207)
858 err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
860 /* Use specific error code for property handling errors.
861 Use loop to provide the right result with tracing */
862 if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
864 svn_error_t *e = err;
866 while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
868 e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED;
873 return svn_error_trace(err);
876 /* Implements svn_ra_serf__request_body_delegate_t */
878 create_empty_put_body(serf_bucket_t **body_bkt,
880 serf_bucket_alloc_t *alloc,
881 apr_pool_t *pool /* request pool */,
882 apr_pool_t *scratch_pool)
884 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
889 setup_put_headers(serf_bucket_t *headers,
891 apr_pool_t *pool /* request pool */,
892 apr_pool_t *scratch_pool)
894 file_context_t *ctx = baton;
896 if (SVN_IS_VALID_REVNUM(ctx->base_revision))
898 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
899 apr_psprintf(pool, "%ld", ctx->base_revision));
902 if (ctx->base_checksum)
904 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
908 if (ctx->result_checksum)
910 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
911 ctx->result_checksum);
914 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx,
915 ctx->relpath, pool));
921 setup_copy_file_headers(serf_bucket_t *headers,
923 apr_pool_t *pool /* request pool */,
924 apr_pool_t *scratch_pool)
926 file_context_t *file = baton;
928 const char *absolute_uri;
930 /* The Dest URI must be absolute. Bummer. */
931 uri = file->commit_ctx->session->session_url;
932 uri.path = (char*)file->url;
933 absolute_uri = apr_uri_unparse(pool, &uri, 0);
935 serf_bucket_headers_set(headers, "Destination", absolute_uri);
937 serf_bucket_headers_setn(headers, "Overwrite", "F");
943 setup_if_header_recursive(svn_boolean_t *added,
944 serf_bucket_t *headers,
945 commit_context_t *commit_ctx,
946 const char *rq_relpath,
949 svn_stringbuf_t *sb = NULL;
950 apr_hash_index_t *hi;
951 apr_pool_t *iterpool = NULL;
953 if (!commit_ctx->lock_tokens)
959 /* We try to create a directory, so within the Subversion world that
960 would imply that there is nothing here, but mod_dav_svn still sees
961 locks on the old nodes here as in DAV it is perfectly legal to lock
962 something that is not there...
964 Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
965 the locks we know of with the request */
967 for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
969 hi = apr_hash_next(hi))
971 const char *relpath = apr_hash_this_key(hi);
974 if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
976 else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
978 /* When a path is already explicit deleted then its lock
979 will be removed by mod_dav. But mod_dav doesn't remove
980 locks on descendants */
985 iterpool = svn_pool_create(pool);
987 svn_pool_clear(iterpool);
990 sb = svn_stringbuf_create("", pool);
992 svn_stringbuf_appendbyte(sb, ' ');
994 uri = commit_ctx->session->session_url;
995 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
998 svn_stringbuf_appendbyte(sb, '<');
999 svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1000 svn_stringbuf_appendcstr(sb, "> (<");
1001 svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi));
1002 svn_stringbuf_appendcstr(sb, ">)");
1006 svn_pool_destroy(iterpool);
1010 serf_bucket_headers_set(headers, "If", sb->data);
1016 return SVN_NO_ERROR;
1019 static svn_error_t *
1020 setup_add_dir_common_headers(serf_bucket_t *headers,
1022 apr_pool_t *pool /* request pool */,
1023 apr_pool_t *scratch_pool)
1025 dir_context_t *dir = baton;
1026 svn_boolean_t added;
1028 return svn_error_trace(
1029 setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath,
1033 static svn_error_t *
1034 setup_copy_dir_headers(serf_bucket_t *headers,
1036 apr_pool_t *pool /* request pool */,
1037 apr_pool_t *scratch_pool)
1039 dir_context_t *dir = baton;
1041 const char *absolute_uri;
1043 /* The Dest URI must be absolute. Bummer. */
1044 uri = dir->commit_ctx->session->session_url;
1046 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1048 uri.path = (char *)dir->url;
1052 uri.path = (char *)svn_path_url_add_component2(
1053 dir->parent_dir->working_url,
1056 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1058 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1060 serf_bucket_headers_setn(headers, "Depth", "infinity");
1061 serf_bucket_headers_setn(headers, "Overwrite", "F");
1063 /* Implicitly checkout this dir now. */
1064 dir->working_url = apr_pstrdup(dir->pool, uri.path);
1066 return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool,
1070 static svn_error_t *
1071 setup_delete_headers(serf_bucket_t *headers,
1073 apr_pool_t *pool /* request pool */,
1074 apr_pool_t *scratch_pool)
1076 delete_context_t *del = baton;
1077 svn_boolean_t added;
1079 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1080 apr_ltoa(pool, del->revision));
1082 if (! del->non_recursive_if)
1083 SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx,
1084 del->relpath, pool));
1087 SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx,
1088 del->relpath, pool));
1092 if (added && del->commit_ctx->keep_locks)
1093 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1094 SVN_DAV_OPTION_KEEP_LOCKS);
1096 return SVN_NO_ERROR;
1099 /* POST against 'me' resource handlers. */
1101 /* Implements svn_ra_serf__request_body_delegate_t */
1102 static svn_error_t *
1103 create_txn_post_body(serf_bucket_t **body_bkt,
1105 serf_bucket_alloc_t *alloc,
1106 apr_pool_t *pool /* request pool */,
1107 apr_pool_t *scratch_pool)
1109 apr_hash_t *revprops = baton;
1110 svn_skel_t *request_skel;
1111 svn_stringbuf_t *skel_str;
1113 request_skel = svn_skel__make_empty_list(pool);
1116 svn_skel_t *proplist_skel;
1118 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1119 svn_skel__prepend(proplist_skel, request_skel);
1120 svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1121 skel_str = svn_skel__unparse(request_skel, pool);
1122 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1126 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1129 return SVN_NO_ERROR;
1132 /* Implements svn_ra_serf__request_header_delegate_t */
1133 static svn_error_t *
1134 setup_post_headers(serf_bucket_t *headers,
1136 apr_pool_t *pool /* request pool */,
1137 apr_pool_t *scratch_pool)
1139 #ifdef SVN_DAV_SEND_VTXN_NAME
1140 /* Enable this to exercise the VTXN-NAME code based on a client
1141 supplied transaction name. */
1142 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1143 svn_uuid_generate(pool));
1146 return SVN_NO_ERROR;
1150 /* Handler baton for POST request. */
1151 typedef struct post_response_ctx_t
1153 svn_ra_serf__handler_t *handler;
1154 commit_context_t *commit_ctx;
1155 } post_response_ctx_t;
1158 /* This implements serf_bucket_headers_do_callback_fn_t. */
1160 post_headers_iterator_callback(void *baton,
1164 post_response_ctx_t *prc = baton;
1165 commit_context_t *prc_cc = prc->commit_ctx;
1166 svn_ra_serf__session_t *sess = prc_cc->session;
1168 /* If we provided a UUID to the POST request, we should get back
1169 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1170 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
1173 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1175 /* Build out txn and txn-root URLs using the txn name we're
1176 given, and store the whole lot of it in the commit context. */
1178 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1179 prc_cc->txn_root_url =
1180 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1183 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1185 /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1186 given, and store the whole lot of it in the commit context. */
1188 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1189 prc_cc->txn_root_url =
1190 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1197 /* A custom serf_response_handler_t which is mostly a wrapper around
1198 svn_ra_serf__expect_empty_body -- it just notices POST response
1201 Implements svn_ra_serf__response_handler_t */
1202 static svn_error_t *
1203 post_response_handler(serf_request_t *request,
1204 serf_bucket_t *response,
1206 apr_pool_t *scratch_pool)
1208 post_response_ctx_t *prc = baton;
1209 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1211 /* Then see which ones we can discover. */
1212 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1214 /* Execute the 'real' response handler to XML-parse the repsonse body. */
1215 return svn_ra_serf__expect_empty_body(request, response,
1216 prc->handler, scratch_pool);
1221 /* Commit baton callbacks */
1223 static svn_error_t *
1224 open_root(void *edit_baton,
1225 svn_revnum_t base_revision,
1226 apr_pool_t *dir_pool,
1229 commit_context_t *commit_ctx = edit_baton;
1230 svn_ra_serf__handler_t *handler;
1231 proppatch_context_t *proppatch_ctx;
1233 apr_hash_index_t *hi;
1234 const char *proppatch_target = NULL;
1235 apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
1237 commit_ctx->open_batons++;
1239 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
1241 post_response_ctx_t *prc;
1242 const char *rel_path;
1243 svn_boolean_t post_with_revprops
1244 = (NULL != svn_hash_gets(commit_ctx->session->supported_posts,
1245 "create-txn-with-props"));
1247 /* Create our activity URL now on the server. */
1248 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1250 handler->method = "POST";
1251 handler->body_type = SVN_SKEL_MIME_TYPE;
1252 handler->body_delegate = create_txn_post_body;
1253 handler->body_delegate_baton =
1254 post_with_revprops ? commit_ctx->revprop_table : NULL;
1255 handler->header_delegate = setup_post_headers;
1256 handler->header_delegate_baton = NULL;
1257 handler->path = commit_ctx->session->me_resource;
1259 prc = apr_pcalloc(scratch_pool, sizeof(*prc));
1260 prc->handler = handler;
1261 prc->commit_ctx = commit_ctx;
1263 handler->response_handler = post_response_handler;
1264 handler->response_baton = prc;
1266 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1268 if (handler->sline.code != 201)
1269 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1271 if (! (commit_ctx->txn_root_url && commit_ctx->txn_url))
1273 return svn_error_createf(
1274 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1275 _("POST request did not return transaction information"));
1278 /* Fixup the txn_root_url to point to the anchor of the commit. */
1279 SVN_ERR(svn_ra_serf__get_relative_path(
1281 commit_ctx->session->session_url.path,
1282 commit_ctx->session,
1284 commit_ctx->txn_root_url = svn_path_url_add_component2(
1285 commit_ctx->txn_root_url,
1286 rel_path, commit_ctx->pool);
1288 /* Build our directory baton. */
1289 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1290 dir->pool = dir_pool;
1291 dir->commit_ctx = commit_ctx;
1292 dir->base_revision = base_revision;
1295 dir->prop_changes = apr_hash_make(dir->pool);
1296 dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url);
1298 /* If we included our revprops in the POST, we need not
1300 proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url;
1304 const char *activity_str = commit_ctx->session->activity_collection_url;
1307 SVN_ERR(svn_ra_serf__v1_get_activity_collection(
1309 commit_ctx->session,
1310 scratch_pool, scratch_pool));
1312 commit_ctx->activity_url = svn_path_url_add_component2(
1314 svn_uuid_generate(scratch_pool),
1317 /* Create our activity URL now on the server. */
1318 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1320 handler->method = "MKACTIVITY";
1321 handler->path = commit_ctx->activity_url;
1323 handler->response_handler = svn_ra_serf__expect_empty_body;
1324 handler->response_baton = handler;
1326 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1328 if (handler->sline.code != 201)
1329 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1331 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1332 SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url),
1333 commit_ctx->session, scratch_pool));
1336 /* Build our directory baton. */
1337 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1338 dir->pool = dir_pool;
1339 dir->commit_ctx = commit_ctx;
1340 dir->base_revision = base_revision;
1343 dir->prop_changes = apr_hash_make(dir->pool);
1345 SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session,
1347 dir->base_revision, commit_ctx->checked_in_url,
1348 dir->pool, scratch_pool));
1349 commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url);
1351 /* Checkout our root dir */
1352 SVN_ERR(checkout_dir(dir, scratch_pool));
1354 proppatch_target = commit_ctx->baseline_url;
1357 /* Unless this is NULL -- which means we don't need to PROPPATCH the
1358 transaction with our revprops -- then, you know, PROPPATCH the
1359 transaction with our revprops. */
1360 if (proppatch_target)
1362 proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx));
1363 proppatch_ctx->pool = scratch_pool;
1364 proppatch_ctx->commit_ctx = NULL; /* No lock info */
1365 proppatch_ctx->path = proppatch_target;
1366 proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool);
1367 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1369 for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table);
1371 hi = apr_hash_next(hi))
1373 svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop));
1375 prop->name = apr_hash_this_key(hi);
1376 prop->value = apr_hash_this_val(hi);
1378 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
1381 SVN_ERR(proppatch_resource(commit_ctx->session,
1382 proppatch_ctx, scratch_pool));
1385 svn_pool_destroy(scratch_pool);
1389 return SVN_NO_ERROR;
1392 /* Implements svn_ra_serf__request_body_delegate_t */
1393 static svn_error_t *
1394 create_delete_body(serf_bucket_t **body_bkt,
1396 serf_bucket_alloc_t *alloc,
1397 apr_pool_t *pool /* request pool */,
1398 apr_pool_t *scratch_pool)
1400 delete_context_t *ctx = baton;
1401 serf_bucket_t *body;
1403 body = serf_bucket_aggregate_create(alloc);
1405 svn_ra_serf__add_xml_header_buckets(body, alloc);
1407 svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens,
1408 ctx->relpath, body, alloc, pool);
1411 return SVN_NO_ERROR;
1414 static svn_error_t *
1415 delete_entry(const char *path,
1416 svn_revnum_t revision,
1420 dir_context_t *dir = parent_baton;
1421 delete_context_t *delete_ctx;
1422 svn_ra_serf__handler_t *handler;
1423 const char *delete_target;
1425 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1427 delete_target = svn_path_url_add_component2(
1428 dir->commit_ctx->txn_root_url,
1433 /* Ensure our directory has been checked out */
1434 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1435 delete_target = svn_path_url_add_component2(dir->working_url,
1436 svn_relpath_basename(path,
1441 /* DELETE our entry */
1442 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1443 delete_ctx->relpath = apr_pstrdup(pool, path);
1444 delete_ctx->revision = revision;
1445 delete_ctx->commit_ctx = dir->commit_ctx;
1447 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1449 handler->response_handler = svn_ra_serf__expect_empty_body;
1450 handler->response_baton = handler;
1452 handler->header_delegate = setup_delete_headers;
1453 handler->header_delegate_baton = delete_ctx;
1455 handler->method = "DELETE";
1456 handler->path = delete_target;
1457 handler->no_fail_on_http_failure_status = TRUE;
1459 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1461 if (handler->sline.code == 400)
1463 /* Try again with non-standard body to overcome Apache Httpd
1465 delete_ctx->non_recursive_if = TRUE;
1467 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1469 handler->response_handler = svn_ra_serf__expect_empty_body;
1470 handler->response_baton = handler;
1472 handler->header_delegate = setup_delete_headers;
1473 handler->header_delegate_baton = delete_ctx;
1475 handler->method = "DELETE";
1476 handler->path = delete_target;
1478 handler->body_type = "text/xml";
1479 handler->body_delegate = create_delete_body;
1480 handler->body_delegate_baton = delete_ctx;
1482 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1485 if (handler->server_error)
1486 return svn_ra_serf__server_error_create(handler, pool);
1488 /* 204 No Content: item successfully deleted */
1489 if (handler->sline.code != 204)
1490 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1492 svn_hash_sets(dir->commit_ctx->deleted_entries,
1493 apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
1495 return SVN_NO_ERROR;
1498 static svn_error_t *
1499 add_directory(const char *path,
1501 const char *copyfrom_path,
1502 svn_revnum_t copyfrom_revision,
1503 apr_pool_t *dir_pool,
1506 dir_context_t *parent = parent_baton;
1508 svn_ra_serf__handler_t *handler;
1509 apr_status_t status;
1510 const char *mkcol_target;
1512 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1514 dir->pool = dir_pool;
1515 dir->parent_dir = parent;
1516 dir->commit_ctx = parent->commit_ctx;
1518 dir->base_revision = SVN_INVALID_REVNUM;
1519 dir->copy_revision = copyfrom_revision;
1520 dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1521 dir->relpath = apr_pstrdup(dir->pool, path);
1522 dir->name = svn_relpath_basename(dir->relpath, NULL);
1523 dir->prop_changes = apr_hash_make(dir->pool);
1525 dir->commit_ctx->open_batons++;
1527 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1529 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1531 mkcol_target = dir->url;
1535 /* Ensure our parent is checked out. */
1536 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1538 dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
1539 dir->name, dir->pool);
1540 mkcol_target = svn_path_url_add_component2(
1541 parent->working_url,
1542 dir->name, dir->pool);
1545 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
1547 handler->response_handler = svn_ra_serf__expect_empty_body;
1548 handler->response_baton = handler;
1549 if (!dir->copy_path)
1551 handler->method = "MKCOL";
1552 handler->path = mkcol_target;
1554 handler->header_delegate = setup_add_dir_common_headers;
1555 handler->header_delegate_baton = dir;
1560 const char *req_url;
1562 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1565 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1566 _("Unable to parse URL '%s'"),
1570 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1571 dir->commit_ctx->session,
1572 uri.path, dir->copy_revision,
1573 dir_pool, dir_pool));
1575 handler->method = "COPY";
1576 handler->path = req_url;
1578 handler->header_delegate = setup_copy_dir_headers;
1579 handler->header_delegate_baton = dir;
1581 /* We have the same problem as with DELETE here: if there are too many
1582 locks, the request fails. But in this case there is no way to retry
1583 with a non-standard request. #### How to fix? */
1584 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1586 if (handler->sline.code != 201)
1587 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1591 return SVN_NO_ERROR;
1594 static svn_error_t *
1595 open_directory(const char *path,
1597 svn_revnum_t base_revision,
1598 apr_pool_t *dir_pool,
1601 dir_context_t *parent = parent_baton;
1604 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1606 dir->pool = dir_pool;
1608 dir->parent_dir = parent;
1609 dir->commit_ctx = parent->commit_ctx;
1612 dir->base_revision = base_revision;
1613 dir->relpath = apr_pstrdup(dir->pool, path);
1614 dir->name = svn_relpath_basename(dir->relpath, NULL);
1615 dir->prop_changes = apr_hash_make(dir->pool);
1617 dir->commit_ctx->open_batons++;
1619 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1621 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1626 SVN_ERR(get_version_url(&dir->url,
1627 dir->commit_ctx->session,
1628 dir->relpath, dir->base_revision,
1629 dir->commit_ctx->checked_in_url,
1630 dir->pool, dir->pool /* scratch_pool */));
1634 return SVN_NO_ERROR;
1637 static svn_error_t *
1638 change_dir_prop(void *dir_baton,
1640 const svn_string_t *value,
1641 apr_pool_t *scratch_pool)
1643 dir_context_t *dir = dir_baton;
1646 if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1648 /* Ensure we have a checked out dir. */
1649 SVN_ERR(checkout_dir(dir, scratch_pool));
1652 prop = apr_palloc(dir->pool, sizeof(*prop));
1654 prop->name = apr_pstrdup(dir->pool, name);
1655 prop->value = svn_string_dup(value, dir->pool);
1657 svn_hash_sets(dir->prop_changes, prop->name, prop);
1659 return SVN_NO_ERROR;
1662 static svn_error_t *
1663 close_directory(void *dir_baton,
1666 dir_context_t *dir = dir_baton;
1668 /* Huh? We're going to be called before the texts are sent. Ugh.
1669 * Therefore, just wave politely at our caller.
1672 /* PROPPATCH our prop change and pass it along. */
1673 if (apr_hash_count(dir->prop_changes))
1675 proppatch_context_t *proppatch_ctx;
1677 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1678 proppatch_ctx->pool = pool;
1679 proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
1680 proppatch_ctx->relpath = dir->relpath;
1681 proppatch_ctx->prop_changes = dir->prop_changes;
1682 proppatch_ctx->base_revision = dir->base_revision;
1684 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1686 proppatch_ctx->path = dir->url;
1690 proppatch_ctx->path = dir->working_url;
1693 SVN_ERR(proppatch_resource(dir->commit_ctx->session,
1694 proppatch_ctx, dir->pool));
1697 dir->commit_ctx->open_batons--;
1699 return SVN_NO_ERROR;
1702 static svn_error_t *
1703 add_file(const char *path,
1705 const char *copy_path,
1706 svn_revnum_t copy_revision,
1707 apr_pool_t *file_pool,
1710 dir_context_t *dir = parent_baton;
1711 file_context_t *new_file;
1712 const char *deleted_parent = path;
1713 apr_pool_t *scratch_pool = svn_pool_create(file_pool);
1715 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1716 new_file->pool = file_pool;
1718 new_file->parent_dir = dir;
1719 new_file->commit_ctx = dir->commit_ctx;
1720 new_file->relpath = apr_pstrdup(new_file->pool, path);
1721 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1722 new_file->added = TRUE;
1723 new_file->base_revision = SVN_INVALID_REVNUM;
1724 new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1725 new_file->copy_revision = copy_revision;
1726 new_file->prop_changes = apr_hash_make(new_file->pool);
1728 dir->commit_ctx->open_batons++;
1730 /* Ensure that the file doesn't exist by doing a HEAD on the
1731 resource. If we're using HTTP v2, we'll just look into the
1732 transaction root tree for this thing. */
1733 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1735 new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
1736 path, new_file->pool);
1740 /* Ensure our parent directory has been checked out */
1741 SVN_ERR(checkout_dir(dir, scratch_pool));
1744 svn_path_url_add_component2(dir->working_url,
1745 new_file->name, new_file->pool);
1748 while (deleted_parent && deleted_parent[0] != '\0')
1750 if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
1754 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1759 svn_ra_serf__handler_t *handler;
1761 const char *req_url;
1762 apr_status_t status;
1764 /* Create the copy directly as cheap 'does exist/out of date'
1765 check. We update the copy (if needed) from close_file() */
1767 status = apr_uri_parse(scratch_pool, copy_path, &uri);
1769 return svn_ra_serf__wrap_err(status, NULL);
1771 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1772 dir->commit_ctx->session,
1773 uri.path, copy_revision,
1774 scratch_pool, scratch_pool));
1776 handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1778 handler->method = "COPY";
1779 handler->path = req_url;
1781 handler->response_handler = svn_ra_serf__expect_empty_body;
1782 handler->response_baton = handler;
1784 handler->header_delegate = setup_copy_file_headers;
1785 handler->header_delegate_baton = new_file;
1787 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1789 if (handler->sline.code != 201)
1790 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1792 else if (! ((dir->added && !dir->copy_path) ||
1793 (deleted_parent && deleted_parent[0] != '\0')))
1795 svn_ra_serf__handler_t *handler;
1798 handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1800 handler->method = "HEAD";
1801 handler->path = svn_path_url_add_component2(
1802 dir->commit_ctx->session->session_url.path,
1803 path, scratch_pool);
1804 handler->response_handler = svn_ra_serf__expect_empty_body;
1805 handler->response_baton = handler;
1806 handler->no_dav_headers = TRUE; /* Read only operation outside txn */
1808 err = svn_ra_serf__context_run_one(handler, scratch_pool);
1810 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1812 svn_error_clear(err); /* Great. We can create a new file! */
1815 return svn_error_trace(err);
1817 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1818 _("File '%s' already exists"), path);
1821 svn_pool_destroy(scratch_pool);
1822 *file_baton = new_file;
1824 return SVN_NO_ERROR;
1827 static svn_error_t *
1828 open_file(const char *path,
1830 svn_revnum_t base_revision,
1831 apr_pool_t *file_pool,
1834 dir_context_t *parent = parent_baton;
1835 file_context_t *new_file;
1837 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1838 new_file->pool = file_pool;
1840 new_file->parent_dir = parent;
1841 new_file->commit_ctx = parent->commit_ctx;
1842 new_file->relpath = apr_pstrdup(new_file->pool, path);
1843 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1844 new_file->added = FALSE;
1845 new_file->base_revision = base_revision;
1846 new_file->prop_changes = apr_hash_make(new_file->pool);
1848 parent->commit_ctx->open_batons++;
1850 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
1852 new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1853 path, new_file->pool);
1857 /* CHECKOUT the file into our activity. */
1858 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1860 new_file->url = new_file->working_url;
1863 *file_baton = new_file;
1865 return SVN_NO_ERROR;
1869 negotiate_put_encoding(int *svndiff_version_p,
1870 int *svndiff_compression_level_p,
1871 svn_ra_serf__session_t *session)
1873 int svndiff_version;
1874 int compression_level;
1876 if (session->using_compression == svn_tristate_unknown)
1878 /* With http-compression=auto, prefer svndiff2 to svndiff1 with a
1879 * low latency connection (assuming the underlying network has high
1880 * bandwidth), as it is faster and in this case, we don't care about
1881 * worse compression ratio.
1883 * Note: For future compatibility, we also handle a theoretically
1884 * possible case where the server has advertised only svndiff2 support.
1886 if (session->supports_svndiff2 &&
1887 svn_ra_serf__is_low_latency_connection(session))
1888 svndiff_version = 2;
1889 else if (session->supports_svndiff1)
1890 svndiff_version = 1;
1891 else if (session->supports_svndiff2)
1892 svndiff_version = 2;
1894 svndiff_version = 0;
1896 else if (session->using_compression == svn_tristate_true)
1898 /* Otherwise, prefer svndiff1, as svndiff2 is not a reasonable
1899 * substitute for svndiff1 with default compression level. (It gives
1900 * better speed and compression ratio comparable to svndiff1 with
1901 * compression level 1, but not 5).
1903 * Note: For future compatibility, we also handle a theoretically
1904 * possible case where the server has advertised only svndiff2 support.
1906 if (session->supports_svndiff1)
1907 svndiff_version = 1;
1908 else if (session->supports_svndiff2)
1909 svndiff_version = 2;
1911 svndiff_version = 0;
1915 /* Difference between svndiff formats 0 and 1/2 that format 1/2 allows
1916 * compression. Uncompressed svndiff0 should also be slightly more
1917 * effective if the compression is not required at all.
1919 * If the server cannot handle svndiff1/2, or compression is disabled
1920 * with the 'http-compression = no' client configuration option, fall
1921 * back to uncompressed svndiff0 format. As a bonus, users can force
1922 * the usage of the uncompressed format by setting the corresponding
1923 * client configuration option, if they want to.
1925 svndiff_version = 0;
1928 if (svndiff_version == 0)
1929 compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
1931 compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
1933 *svndiff_version_p = svndiff_version;
1934 *svndiff_compression_level_p = compression_level;
1937 static svn_error_t *
1938 apply_textdelta(void *file_baton,
1939 const char *base_checksum,
1941 svn_txdelta_window_handler_t *handler,
1942 void **handler_baton)
1944 file_context_t *ctx = file_baton;
1945 int svndiff_version;
1946 int compression_level;
1948 /* Construct a holder for the request body; we'll give it to serf when we
1951 * Please note that if this callback is used, large request bodies will
1952 * be spilled into temporary files (that requires disk space and prevents
1953 * simultaneous processing by the server and the client). A better approach
1954 * that streams the request body is implemented in apply_textdelta_stream().
1955 * It will be used with most recent servers having the "send result checksum
1956 * in response to a PUT" capability, and only if the editor driver uses the
1960 svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
1962 ctx->stream = svn_ra_serf__request_body_get_stream(ctx->svndiff);
1964 negotiate_put_encoding(&svndiff_version, &compression_level,
1965 ctx->commit_ctx->session);
1966 /* Disown the stream; we'll close it explicitly in close_file(). */
1967 svn_txdelta_to_svndiff3(handler, handler_baton,
1968 svn_stream_disown(ctx->stream, pool),
1969 svndiff_version, compression_level, pool);
1972 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
1974 return SVN_NO_ERROR;
1977 typedef struct open_txdelta_baton_t
1979 svn_ra_serf__session_t *session;
1980 svn_txdelta_stream_open_func_t open_func;
1983 } open_txdelta_baton_t;
1986 txdelta_stream_errfunc(void *baton, svn_error_t *err)
1988 open_txdelta_baton_t *b = baton;
1990 /* Remember extended error info from the stream bucket. Note that
1991 * theoretically this errfunc could be called multiple times -- say,
1992 * if the request gets restarted after an error. Compose the errors
1993 * so we don't leak one of them if this happens. */
1994 b->err = svn_error_compose_create(b->err, svn_error_dup(err));
1997 /* Implements svn_ra_serf__request_body_delegate_t */
1998 static svn_error_t *
1999 create_body_from_txdelta_stream(serf_bucket_t **body_bkt,
2001 serf_bucket_alloc_t *alloc,
2002 apr_pool_t *pool /* request pool */,
2003 apr_pool_t *scratch_pool)
2005 open_txdelta_baton_t *b = baton;
2006 svn_txdelta_stream_t *txdelta_stream;
2007 svn_stream_t *stream;
2008 int svndiff_version;
2009 int compression_level;
2011 SVN_ERR(b->open_func(&txdelta_stream, b->open_baton, pool, scratch_pool));
2013 negotiate_put_encoding(&svndiff_version, &compression_level, b->session);
2014 stream = svn_txdelta_to_svndiff_stream(txdelta_stream, svndiff_version,
2015 compression_level, pool);
2016 *body_bkt = svn_ra_serf__create_stream_bucket(stream, alloc,
2017 txdelta_stream_errfunc, b);
2019 return SVN_NO_ERROR;
2022 /* Handler baton for PUT request. */
2023 typedef struct put_response_ctx_t
2025 svn_ra_serf__handler_t *handler;
2026 file_context_t *file_ctx;
2027 } put_response_ctx_t;
2029 /* Implements svn_ra_serf__response_handler_t */
2030 static svn_error_t *
2031 put_response_handler(serf_request_t *request,
2032 serf_bucket_t *response,
2034 apr_pool_t *scratch_pool)
2036 put_response_ctx_t *prc = baton;
2037 serf_bucket_t *hdrs;
2040 hdrs = serf_bucket_response_get_headers(response);
2041 val = serf_bucket_headers_get(hdrs, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
2042 SVN_ERR(svn_checksum_parse_hex(&prc->file_ctx->remote_result_checksum,
2043 svn_checksum_md5, val, prc->file_ctx->pool));
2045 return svn_error_trace(
2046 svn_ra_serf__expect_empty_body(request, response,
2047 prc->handler, scratch_pool));
2050 static svn_error_t *
2051 apply_textdelta_stream(const svn_delta_editor_t *editor,
2053 const char *base_checksum,
2054 svn_txdelta_stream_open_func_t open_func,
2056 apr_pool_t *scratch_pool)
2058 file_context_t *ctx = file_baton;
2059 open_txdelta_baton_t open_txdelta_baton = {0};
2060 svn_ra_serf__handler_t *handler;
2061 put_response_ctx_t *prc;
2062 int expected_result;
2065 /* Remember that we have sent the svndiff. A case when we need to
2066 * perform a zero-byte file PUT (during add_file, close_file editor
2067 * sequences) is handled in close_file().
2069 ctx->svndiff_sent = TRUE;
2070 ctx->base_checksum = base_checksum;
2072 handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
2074 handler->method = "PUT";
2075 handler->path = ctx->url;
2077 prc = apr_pcalloc(scratch_pool, sizeof(*prc));
2078 prc->handler = handler;
2079 prc->file_ctx = ctx;
2081 handler->response_handler = put_response_handler;
2082 handler->response_baton = prc;
2084 open_txdelta_baton.session = ctx->commit_ctx->session;
2085 open_txdelta_baton.open_func = open_func;
2086 open_txdelta_baton.open_baton = open_baton;
2087 open_txdelta_baton.err = SVN_NO_ERROR;
2089 handler->body_delegate = create_body_from_txdelta_stream;
2090 handler->body_delegate_baton = &open_txdelta_baton;
2091 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2093 handler->header_delegate = setup_put_headers;
2094 handler->header_delegate_baton = ctx;
2096 err = svn_ra_serf__context_run_one(handler, scratch_pool);
2097 /* Do we have an error from the stream bucket? If yes, use it. */
2098 if (open_txdelta_baton.err)
2100 svn_error_clear(err);
2101 return svn_error_trace(open_txdelta_baton.err);
2104 return svn_error_trace(err);
2106 if (ctx->added && !ctx->copy_path)
2107 expected_result = 201; /* Created */
2109 expected_result = 204; /* Updated */
2111 if (handler->sline.code != expected_result)
2112 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2114 return SVN_NO_ERROR;
2117 static svn_error_t *
2118 change_file_prop(void *file_baton,
2120 const svn_string_t *value,
2123 file_context_t *file = file_baton;
2126 prop = apr_palloc(file->pool, sizeof(*prop));
2128 prop->name = apr_pstrdup(file->pool, name);
2129 prop->value = svn_string_dup(value, file->pool);
2131 svn_hash_sets(file->prop_changes, prop->name, prop);
2133 return SVN_NO_ERROR;
2136 static svn_error_t *
2137 close_file(void *file_baton,
2138 const char *text_checksum,
2139 apr_pool_t *scratch_pool)
2141 file_context_t *ctx = file_baton;
2142 svn_boolean_t put_empty_file = FALSE;
2144 ctx->result_checksum = text_checksum;
2146 /* If we got no stream of changes, but this is an added-without-history
2147 * file, make a note that we'll be PUTting a zero-byte file to the server.
2149 if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
2150 put_empty_file = TRUE;
2152 /* If we have a stream of changes, push them to the server... */
2153 if ((ctx->svndiff || put_empty_file) && !ctx->svndiff_sent)
2155 svn_ra_serf__handler_t *handler;
2156 int expected_result;
2158 handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
2161 handler->method = "PUT";
2162 handler->path = ctx->url;
2164 handler->response_handler = svn_ra_serf__expect_empty_body;
2165 handler->response_baton = handler;
2169 handler->body_delegate = create_empty_put_body;
2170 handler->body_delegate_baton = ctx;
2171 handler->body_type = "text/plain";
2175 SVN_ERR(svn_stream_close(ctx->stream));
2177 svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
2178 &handler->body_delegate_baton,
2180 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2183 handler->header_delegate = setup_put_headers;
2184 handler->header_delegate_baton = ctx;
2186 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2188 if (ctx->added && ! ctx->copy_path)
2189 expected_result = 201; /* Created */
2191 expected_result = 204; /* Updated */
2193 if (handler->sline.code != expected_result)
2194 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2197 /* Don't keep open file handles longer than necessary. */
2199 SVN_ERR(svn_ra_serf__request_body_cleanup(ctx->svndiff, scratch_pool));
2201 /* If we had any prop changes, push them via PROPPATCH. */
2202 if (apr_hash_count(ctx->prop_changes))
2204 proppatch_context_t *proppatch;
2206 proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
2207 proppatch->pool = scratch_pool;
2208 proppatch->relpath = ctx->relpath;
2209 proppatch->path = ctx->url;
2210 proppatch->commit_ctx = ctx->commit_ctx;
2211 proppatch->prop_changes = ctx->prop_changes;
2212 proppatch->base_revision = ctx->base_revision;
2214 SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
2215 proppatch, scratch_pool));
2218 if (ctx->result_checksum && ctx->remote_result_checksum)
2220 svn_checksum_t *result_checksum;
2222 SVN_ERR(svn_checksum_parse_hex(&result_checksum, svn_checksum_md5,
2223 ctx->result_checksum, scratch_pool));
2225 if (!svn_checksum_match(result_checksum, ctx->remote_result_checksum))
2226 return svn_checksum_mismatch_err(result_checksum,
2227 ctx->remote_result_checksum,
2229 _("Checksum mismatch for '%s'"),
2230 svn_dirent_local_style(ctx->relpath,
2234 ctx->commit_ctx->open_batons--;
2236 return SVN_NO_ERROR;
2239 static svn_error_t *
2240 close_edit(void *edit_baton,
2243 commit_context_t *ctx = edit_baton;
2244 const char *merge_target =
2245 ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2246 const svn_commit_info_t *commit_info;
2247 svn_error_t *err = NULL;
2249 if (ctx->open_batons > 0)
2250 return svn_error_create(
2251 SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
2252 _("Closing editor with directories or files open"));
2254 /* MERGE our activity */
2255 SVN_ERR(svn_ra_serf__run_merge(&commit_info,
2262 ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
2264 /* Inform the WC that we did a commit. */
2266 err = ctx->callback(commit_info, ctx->callback_baton, pool);
2268 /* If we're using activities, DELETE our completed activity. */
2269 if (ctx->activity_url)
2271 svn_ra_serf__handler_t *handler;
2273 handler = svn_ra_serf__create_handler(ctx->session, pool);
2275 handler->method = "DELETE";
2276 handler->path = ctx->activity_url;
2278 handler->response_handler = svn_ra_serf__expect_empty_body;
2279 handler->response_baton = handler;
2281 ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
2283 SVN_ERR(svn_error_compose_create(
2285 svn_ra_serf__context_run_one(handler, pool)));
2287 if (handler->sline.code != 204)
2288 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2293 return SVN_NO_ERROR;
2296 static svn_error_t *
2297 abort_edit(void *edit_baton,
2300 commit_context_t *ctx = edit_baton;
2301 svn_ra_serf__handler_t *handler;
2303 /* If an activity or transaction wasn't even created, don't bother
2304 trying to delete it. */
2305 if (! (ctx->activity_url || ctx->txn_url))
2306 return SVN_NO_ERROR;
2308 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2309 had a problem. We need to reset it, in order to use it again. */
2310 serf_connection_reset(ctx->session->conns[0]->conn);
2312 /* DELETE our aborted activity */
2313 handler = svn_ra_serf__create_handler(ctx->session, pool);
2315 handler->method = "DELETE";
2317 handler->response_handler = svn_ra_serf__expect_empty_body;
2318 handler->response_baton = handler;
2319 handler->no_fail_on_http_failure_status = TRUE;
2321 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2322 handler->path = ctx->txn_url;
2324 handler->path = ctx->activity_url;
2326 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2329 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2330 404 if the activity wasn't found. */
2331 if (handler->sline.code != 204
2332 && handler->sline.code != 403
2333 && handler->sline.code != 404)
2335 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2338 /* Don't delete again if somebody aborts twice */
2339 ctx->activity_url = NULL;
2340 ctx->txn_url = NULL;
2342 return SVN_NO_ERROR;
2346 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2347 const svn_delta_editor_t **ret_editor,
2349 apr_hash_t *revprop_table,
2350 svn_commit_callback2_t callback,
2351 void *callback_baton,
2352 apr_hash_t *lock_tokens,
2353 svn_boolean_t keep_locks,
2356 svn_ra_serf__session_t *session = ra_session->priv;
2357 svn_delta_editor_t *editor;
2358 commit_context_t *ctx;
2359 const char *repos_root;
2360 const char *base_relpath;
2361 svn_boolean_t supports_ephemeral_props;
2363 ctx = apr_pcalloc(pool, sizeof(*ctx));
2367 ctx->session = session;
2369 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2371 /* If the server supports ephemeral properties, add some carrying
2372 interesting version information. */
2373 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2374 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2376 if (supports_ephemeral_props)
2378 svn_hash_sets(ctx->revprop_table,
2379 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2380 svn_string_create(SVN_VER_NUMBER, pool));
2381 svn_hash_sets(ctx->revprop_table,
2382 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2383 svn_string_create(session->useragent, pool));
2386 ctx->callback = callback;
2387 ctx->callback_baton = callback_baton;
2389 ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2390 ? lock_tokens : NULL;
2391 ctx->keep_locks = keep_locks;
2393 ctx->deleted_entries = apr_hash_make(ctx->pool);
2395 editor = svn_delta_default_editor(pool);
2396 editor->open_root = open_root;
2397 editor->delete_entry = delete_entry;
2398 editor->add_directory = add_directory;
2399 editor->open_directory = open_directory;
2400 editor->change_dir_prop = change_dir_prop;
2401 editor->close_directory = close_directory;
2402 editor->add_file = add_file;
2403 editor->open_file = open_file;
2404 editor->apply_textdelta = apply_textdelta;
2405 editor->change_file_prop = change_file_prop;
2406 editor->close_file = close_file;
2407 editor->close_edit = close_edit;
2408 editor->abort_edit = abort_edit;
2409 /* Only install the callback that allows streaming PUT request bodies
2410 * if the server has the necessary capability. Otherwise, this will
2411 * fallback to the default implementation using the temporary files.
2412 * See default_editor.c:apply_textdelta_stream(). */
2413 if (session->supports_put_result_checksum)
2414 editor->apply_textdelta_stream = apply_textdelta_stream;
2416 *ret_editor = editor;
2419 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2420 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2423 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2424 *edit_baton, repos_root, base_relpath,
2425 session->shim_callbacks, pool, pool));
2427 return SVN_NO_ERROR;
2431 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2434 const svn_string_t *const *old_value_p,
2435 const svn_string_t *value,
2438 svn_ra_serf__session_t *session = ra_session->priv;
2439 proppatch_context_t *proppatch_ctx;
2440 const char *proppatch_target;
2441 const svn_string_t *tmp_old_value;
2442 svn_boolean_t atomic_capable = FALSE;
2446 if (old_value_p || !value)
2447 SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable,
2448 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2453 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2454 SVN_ERR_ASSERT(atomic_capable);
2456 else if (! value && atomic_capable)
2458 svn_string_t *old_value;
2459 /* mod_dav_svn doesn't report a failure when a property delete fails. The
2460 atomic revprop change behavior is a nice workaround, to allow getting
2461 access to the error anyway.
2463 Somehow the mod_dav maintainers think that returning an error from
2464 mod_dav's property delete is an RFC violation.
2465 See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */
2467 SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value,
2471 return SVN_NO_ERROR; /* Nothing to delete */
2473 /* The api expects a double const pointer. Let's make one */
2474 tmp_old_value = old_value;
2475 old_value_p = &tmp_old_value;
2478 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2480 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2484 const char *vcc_url;
2486 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
2488 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2489 session, vcc_url, rev, "href",
2493 /* PROPPATCH our log message and pass it along. */
2494 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2495 proppatch_ctx->pool = pool;
2496 proppatch_ctx->commit_ctx = NULL; /* No lock headers */
2497 proppatch_ctx->path = proppatch_target;
2498 proppatch_ctx->prop_changes = apr_hash_make(pool);
2499 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2503 prop = apr_palloc(pool, sizeof (*prop));
2506 prop->value = *old_value_p;
2508 proppatch_ctx->old_props = apr_hash_make(pool);
2509 svn_hash_sets(proppatch_ctx->old_props, prop->name, prop);
2512 prop = apr_palloc(pool, sizeof (*prop));
2515 prop->value = value;
2516 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
2518 err = proppatch_resource(session, proppatch_ctx, pool);
2520 /* Use specific error code for old property value mismatch.
2521 Use loop to provide the right result with tracing */
2522 if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2524 svn_error_t *e = err;
2526 while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2528 e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
2533 return svn_error_trace(err);