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 */
76 #define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
78 /* Structure associated with a PROPPATCH request. */
79 typedef struct proppatch_context_t {
85 commit_context_t *commit_ctx;
87 /* Changed properties. const char * -> svn_prop_t * */
88 apr_hash_t *prop_changes;
90 /* Same, for the old value, or NULL. */
91 apr_hash_t *old_props;
93 /* In HTTP v2, this is the file/directory version we think we're changing. */
94 svn_revnum_t base_revision;
96 } proppatch_context_t;
98 typedef struct delete_context_t {
101 svn_revnum_t revision;
103 commit_context_t *commit_ctx;
105 svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */
108 /* Represents a directory. */
109 typedef struct dir_context_t {
110 /* Pool for our directory. */
113 /* The root commit we're in progress for. */
114 commit_context_t *commit_ctx;
116 /* URL to operate against (used for CHECKOUT and PROPPATCH before
117 HTTP v2, for PROPPATCH in HTTP v2). */
120 /* How many pending changes we have left in this directory. */
121 unsigned int ref_count;
123 /* Is this directory being added? (Otherwise, just opened.) */
127 struct dir_context_t *parent_dir;
129 /* The directory name; if "", we're the 'root' */
132 /* The basename of the directory. "" for the 'root' */
135 /* The base revision of the dir. */
136 svn_revnum_t base_revision;
138 const char *copy_path;
139 svn_revnum_t copy_revision;
141 /* Changed properties (const char * -> svn_prop_t *) */
142 apr_hash_t *prop_changes;
144 /* The checked-out working resource for this directory. May be NULL; if so
145 call checkout_dir() first. */
146 const char *working_url;
149 /* Represents a file to be committed. */
150 typedef struct file_context_t {
151 /* Pool for our file. */
154 /* The root commit we're in progress for. */
155 commit_context_t *commit_ctx;
157 /* Is this file being added? (Otherwise, just opened.) */
160 dir_context_t *parent_dir;
165 /* The checked-out working resource for this file. */
166 const char *working_url;
168 /* The base revision of the file. */
169 svn_revnum_t base_revision;
171 /* Copy path and revision */
172 const char *copy_path;
173 svn_revnum_t copy_revision;
176 svn_stream_t *stream;
178 /* Temporary file containing the svndiff. */
181 /* Our base checksum as reported by the WC. */
182 const char *base_checksum;
184 /* Our resulting checksum as reported by the WC. */
185 const char *result_checksum;
187 /* Changed properties (const char * -> svn_prop_t *) */
188 apr_hash_t *prop_changes;
190 /* URL to PUT the file at. */
196 /* Setup routines and handlers for various requests we'll invoke. */
198 /* Implements svn_ra_serf__request_body_delegate_t */
200 create_checkout_body(serf_bucket_t **bkt,
202 serf_bucket_alloc_t *alloc,
203 apr_pool_t *pool /* request pool */,
204 apr_pool_t *scratch_pool)
206 const char *activity_url = baton;
207 serf_bucket_t *body_bkt;
209 body_bkt = serf_bucket_aggregate_create(alloc);
211 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
212 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
215 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set",
217 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href",
220 SVN_ERR_ASSERT(activity_url != NULL);
221 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
223 strlen(activity_url));
225 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
226 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
227 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
228 "D:apply-to-version", SVN_VA_NULL);
229 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
236 /* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
237 given COMMIT_CTX. The resulting working resource will be returned in
238 *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
239 are performed in SCRATCH_POOL.
241 ### are these URLs actually repos relpath values? or fspath? or maybe
242 ### the abspath portion of the full URL.
244 This function operates synchronously.
246 Strictly speaking, we could perform "all" of the CHECKOUT requests
247 when the commit starts, and only block when we need a specific
248 answer. Or, at a minimum, send off these individual requests async
249 and block when we need the answer (eg PUT or PROPPATCH).
251 However: the investment to speed this up is not worthwhile, given
252 that CHECKOUT (and the related round trip) is completely obviated
256 checkout_node(const char **working_url,
257 const commit_context_t *commit_ctx,
258 const char *node_url,
259 apr_pool_t *result_pool,
260 apr_pool_t *scratch_pool)
262 svn_ra_serf__handler_t *handler;
266 /* HANDLER_POOL is the scratch pool since we don't need to remember
267 anything from the handler. We just want the working resource. */
268 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
270 handler->body_delegate = create_checkout_body;
271 handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
272 handler->body_type = "text/xml";
274 handler->response_handler = svn_ra_serf__expect_empty_body;
275 handler->response_baton = handler;
277 handler->method = "CHECKOUT";
278 handler->path = node_url;
280 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
282 if (handler->sline.code != 201)
283 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
285 if (handler->location == NULL)
286 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
287 _("No Location header received"));
289 /* We only want the path portion of the Location header.
290 (code.google.com sometimes returns an 'http:' scheme for an
291 'https:' transaction ... we'll work around that by stripping the
292 scheme, host, and port here and re-adding the correct ones
294 status = apr_uri_parse(scratch_pool, handler->location, &uri);
296 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
297 _("Error parsing Location header value"));
299 *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
305 /* This is a wrapper around checkout_node() (which see for
306 documentation) which simply retries the CHECKOUT request when it
307 fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
310 See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
314 retry_checkout_node(const char **working_url,
315 const commit_context_t *commit_ctx,
316 const char *node_url,
317 apr_pool_t *result_pool,
318 apr_pool_t *scratch_pool)
320 svn_error_t *err = SVN_NO_ERROR;
321 int retry_count = 5; /* Magic, arbitrary number. */
325 svn_error_clear(err);
327 err = checkout_node(working_url, commit_ctx, node_url,
328 result_pool, scratch_pool);
330 /* There's a small chance of a race condition here if Apache is
331 experiencing heavy commit concurrency or if the network has
332 long latency. It's possible that the value of HEAD changed
333 between the time we fetched the latest baseline and the time
334 we try to CHECKOUT that baseline. If that happens, Apache
335 will throw us a BAD_BASELINE error (deltaV says you can only
336 checkout the latest baseline). We just ignore that specific
337 error and retry a few times, asking for the latest baseline
339 if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
340 return svn_error_trace(err);
342 while (err && retry_count--);
344 return svn_error_trace(err);
349 checkout_dir(dir_context_t *dir,
350 apr_pool_t *scratch_pool)
352 dir_context_t *c_dir = dir;
353 const char *checkout_url;
354 const char **working;
356 if (dir->working_url)
361 /* Is this directory or one of our parent dirs newly added?
362 * If so, we're already implicitly checked out. */
367 /* Calculate the working_url by skipping the shared ancestor between
368 * the c_dir_parent->relpath and dir->relpath. This is safe since an
369 * add is guaranteed to have a parent that is checked out. */
370 dir_context_t *c_dir_parent = c_dir->parent_dir;
371 const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath,
374 /* Implicitly checkout this dir now. */
375 SVN_ERR_ASSERT(c_dir_parent->working_url);
376 dir->working_url = svn_path_url_add_component2(
377 c_dir_parent->working_url,
381 c_dir = c_dir->parent_dir;
384 /* We could be called twice for the root: once to checkout the baseline;
385 * once to checkout the directory itself if we need to do so.
386 * Note: CHECKOUT_URL should live longer than HANDLER.
388 if (!dir->parent_dir && !dir->commit_ctx->baseline_url)
390 checkout_url = dir->commit_ctx->vcc_url;
391 working = &dir->commit_ctx->baseline_url;
395 checkout_url = dir->url;
396 working = &dir->working_url;
399 /* Checkout our directory into the activity URL now. */
400 return svn_error_trace(retry_checkout_node(working, dir->commit_ctx,
402 dir->pool, scratch_pool));
406 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
407 * RELPATH (relative to the root of SESSION).
409 * Try to find this version url in three ways:
410 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
411 * version url from the working copy properties.
412 * Second, if the version url of the parent directory PARENT_VSN_URL is
413 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
415 * Else, fetch the version url for the root of SESSION using CONN and
416 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
419 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
420 * temporary allocation.
423 get_version_url(const char **checked_in_url,
424 svn_ra_serf__session_t *session,
426 svn_revnum_t base_revision,
427 const char *parent_vsn_url,
428 apr_pool_t *result_pool,
429 apr_pool_t *scratch_pool)
431 const char *root_checkout;
433 if (session->wc_callbacks->get_wc_prop)
435 const svn_string_t *current_version;
437 SVN_ERR(session->wc_callbacks->get_wc_prop(
438 session->wc_callback_baton,
440 SVN_RA_SERF__WC_CHECKED_IN_URL,
441 ¤t_version, scratch_pool));
446 svn_urlpath__canonicalize(current_version->data, result_pool);
453 root_checkout = parent_vsn_url;
457 const char *propfind_url;
459 if (SVN_IS_VALID_REVNUM(base_revision))
461 /* mod_dav_svn can't handle the "Label:" header that
462 svn_ra_serf__deliver_props() is going to try to use for
463 this lookup, so we'll do things the hard(er) way, by
464 looking up the version URL from a resource in the
465 baseline collection. */
466 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
467 NULL /* latest_revnum */,
469 NULL /* url */, base_revision,
470 scratch_pool, scratch_pool));
474 propfind_url = session->session_url.path;
477 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session,
478 propfind_url, base_revision,
480 scratch_pool, scratch_pool));
482 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
483 _("Path '%s' not present"),
484 session->session_url.path);
486 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
489 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
496 checkout_file(file_context_t *file,
497 apr_pool_t *scratch_pool)
499 dir_context_t *parent_dir = file->parent_dir;
500 const char *checkout_url;
502 /* Is one of our parent dirs newly added? If so, we're already
503 * implicitly checked out.
507 if (parent_dir->added)
509 /* Implicitly checkout this file now. */
510 SVN_ERR_ASSERT(parent_dir->working_url);
511 file->working_url = svn_path_url_add_component2(
512 parent_dir->working_url,
513 svn_relpath_skip_ancestor(
514 parent_dir->relpath, file->relpath),
518 parent_dir = parent_dir->parent_dir;
521 SVN_ERR(get_version_url(&checkout_url,
522 file->commit_ctx->session,
523 file->relpath, file->base_revision,
524 NULL, scratch_pool, scratch_pool));
526 /* Checkout our file into the activity URL now. */
527 return svn_error_trace(retry_checkout_node(&file->working_url,
528 file->commit_ctx, checkout_url,
529 file->pool, scratch_pool));
532 /* Helper function for proppatch_walker() below. */
534 get_encoding_and_cdata(const char **encoding_p,
535 const svn_string_t **encoded_value_p,
536 serf_bucket_alloc_t *alloc,
537 const svn_string_t *value,
538 apr_pool_t *result_pool,
539 apr_pool_t *scratch_pool)
544 *encoded_value_p = NULL;
548 /* If a property is XML-safe, XML-encode it. Else, base64-encode
550 if (svn_xml_is_xml_safe(value->data, value->len))
552 svn_stringbuf_t *xml_esc = NULL;
553 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
555 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
559 *encoding_p = "base64";
560 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
566 /* Helper for create_proppatch_body. Writes per property xml to body */
568 write_prop_xml(const proppatch_context_t *proppatch,
569 serf_bucket_t *body_bkt,
570 serf_bucket_alloc_t *alloc,
571 const svn_prop_t *prop,
572 apr_pool_t *result_pool,
573 apr_pool_t *scratch_pool)
575 serf_bucket_t *cdata_bkt;
576 const char *encoding;
577 const svn_string_t *encoded_value;
578 const char *prop_name;
579 const svn_prop_t *old_prop;
581 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value,
582 result_pool, scratch_pool));
585 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
594 /* Use the namespace prefix instead of adding the xmlns attribute to support
595 property names containing ':' */
596 if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
598 prop_name = apr_pstrcat(result_pool,
599 "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1,
604 prop_name = apr_pstrcat(result_pool,
610 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
611 "V:encoding", encoding,
614 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
615 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
618 old_prop = proppatch->old_props
619 ? svn_hash_gets(proppatch->old_props, prop->name)
623 const char *encoding2;
624 const svn_string_t *encoded_value2;
625 serf_bucket_t *cdata_bkt2;
627 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
628 alloc, old_prop->value,
629 result_pool, scratch_pool));
633 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
643 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
644 "V:" SVN_DAV__OLD_VALUE,
645 "V:encoding", encoding2,
648 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
649 "V:" SVN_DAV__OLD_VALUE,
650 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
654 serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
656 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
657 "V:" SVN_DAV__OLD_VALUE);
660 serf_bucket_aggregate_append(body_bkt, cdata_bkt);
661 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
666 /* Possible add the lock-token "If:" precondition header to HEADERS if
667 an examination of COMMIT_CTX and RELPATH indicates that this is the
670 Generally speaking, if the client provided a lock token for
671 RELPATH, it's the right thing to do. There is a notable instance
672 where this is not the case, however. If the file at RELPATH was
673 explicitly deleted in this commit already, then mod_dav removed its
674 lock token when it fielded the DELETE request, so we don't want to
675 set the lock precondition again. (See
676 http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
679 maybe_set_lock_token_header(serf_bucket_t *headers,
680 commit_context_t *commit_ctx,
686 if (! (*relpath && commit_ctx->lock_tokens))
689 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
691 token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
694 const char *token_header;
695 const char *token_uri;
696 apr_uri_t uri = commit_ctx->session->session_url;
698 /* Supplying the optional URI affects apache response when
699 the lock is broken, see issue 4369. When present any URI
700 must be absolute (RFC 2518 9.4). */
701 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
703 token_uri = apr_uri_unparse(pool, &uri, 0);
705 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
707 serf_bucket_headers_set(headers, "If", token_header);
715 setup_proppatch_headers(serf_bucket_t *headers,
717 apr_pool_t *pool /* request pool */,
718 apr_pool_t *scratch_pool)
720 proppatch_context_t *proppatch = baton;
722 if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
724 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
725 apr_psprintf(pool, "%ld",
726 proppatch->base_revision));
729 if (proppatch->relpath && proppatch->commit_ctx)
730 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx,
731 proppatch->relpath, pool));
737 /* Implements svn_ra_serf__request_body_delegate_t */
739 create_proppatch_body(serf_bucket_t **bkt,
741 serf_bucket_alloc_t *alloc,
742 apr_pool_t *pool /* request pool */,
743 apr_pool_t *scratch_pool)
745 proppatch_context_t *ctx = baton;
746 serf_bucket_t *body_bkt;
747 svn_boolean_t opened = FALSE;
748 apr_hash_index_t *hi;
750 body_bkt = serf_bucket_aggregate_create(alloc);
752 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
753 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
755 "xmlns:V", SVN_DAV_PROP_NS_DAV,
756 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
757 "xmlns:S", SVN_DAV_PROP_NS_SVN,
760 /* First we write property SETs */
761 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
763 hi = apr_hash_next(hi))
765 svn_prop_t *prop = apr_hash_this_val(hi);
768 || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
773 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set",
775 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
779 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
780 pool, scratch_pool));
786 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
787 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
790 /* And then property REMOVEs */
793 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
795 hi = apr_hash_next(hi))
797 svn_prop_t *prop = apr_hash_this_val(hi);
800 && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
805 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove",
807 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
811 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
812 pool, scratch_pool));
818 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
819 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
822 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
829 proppatch_resource(svn_ra_serf__session_t *session,
830 proppatch_context_t *proppatch,
833 svn_ra_serf__handler_t *handler;
836 handler = svn_ra_serf__create_handler(session, pool);
838 handler->method = "PROPPATCH";
839 handler->path = proppatch->path;
841 handler->header_delegate = setup_proppatch_headers;
842 handler->header_delegate_baton = proppatch;
844 handler->body_delegate = create_proppatch_body;
845 handler->body_delegate_baton = proppatch;
846 handler->body_type = "text/xml";
848 handler->response_handler = svn_ra_serf__handle_multistatus_only;
849 handler->response_baton = handler;
851 err = svn_ra_serf__context_run_one(handler, pool);
853 if (!err && handler->sline.code != 207)
854 err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
856 /* Use specific error code for property handling errors.
857 Use loop to provide the right result with tracing */
858 if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
860 svn_error_t *e = err;
862 while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
864 e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED;
869 return svn_error_trace(err);
872 /* Implements svn_ra_serf__request_body_delegate_t */
874 create_put_body(serf_bucket_t **body_bkt,
876 serf_bucket_alloc_t *alloc,
877 apr_pool_t *pool /* request pool */,
878 apr_pool_t *scratch_pool)
880 file_context_t *ctx = baton;
883 /* We need to flush the file, make it unbuffered (so that it can be
884 * zero-copied via mmap), and reset the position before attempting to
887 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
888 * and zero-copy the PUT body. However, on older APR versions, we can't
889 * check the buffer status; but serf will fall through and create a file
890 * bucket for us on the buffered svndiff handle.
892 SVN_ERR(svn_io_file_flush(ctx->svndiff, pool));
893 apr_file_buffer_set(ctx->svndiff, NULL, 0);
895 SVN_ERR(svn_io_file_seek(ctx->svndiff, APR_SET, &offset, pool));
897 *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
901 /* Implements svn_ra_serf__request_body_delegate_t */
903 create_empty_put_body(serf_bucket_t **body_bkt,
905 serf_bucket_alloc_t *alloc,
906 apr_pool_t *pool /* request pool */,
907 apr_pool_t *scratch_pool)
909 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
914 setup_put_headers(serf_bucket_t *headers,
916 apr_pool_t *pool /* request pool */,
917 apr_pool_t *scratch_pool)
919 file_context_t *ctx = baton;
921 if (SVN_IS_VALID_REVNUM(ctx->base_revision))
923 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
924 apr_psprintf(pool, "%ld", ctx->base_revision));
927 if (ctx->base_checksum)
929 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
933 if (ctx->result_checksum)
935 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
936 ctx->result_checksum);
939 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx,
940 ctx->relpath, pool));
946 setup_copy_file_headers(serf_bucket_t *headers,
948 apr_pool_t *pool /* request pool */,
949 apr_pool_t *scratch_pool)
951 file_context_t *file = baton;
953 const char *absolute_uri;
955 /* The Dest URI must be absolute. Bummer. */
956 uri = file->commit_ctx->session->session_url;
957 uri.path = (char*)file->url;
958 absolute_uri = apr_uri_unparse(pool, &uri, 0);
960 serf_bucket_headers_set(headers, "Destination", absolute_uri);
962 serf_bucket_headers_setn(headers, "Overwrite", "F");
968 setup_if_header_recursive(svn_boolean_t *added,
969 serf_bucket_t *headers,
970 commit_context_t *commit_ctx,
971 const char *rq_relpath,
974 svn_stringbuf_t *sb = NULL;
975 apr_hash_index_t *hi;
976 apr_pool_t *iterpool = NULL;
978 if (!commit_ctx->lock_tokens)
984 /* We try to create a directory, so within the Subversion world that
985 would imply that there is nothing here, but mod_dav_svn still sees
986 locks on the old nodes here as in DAV it is perfectly legal to lock
987 something that is not there...
989 Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
990 the locks we know of with the request */
992 for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
994 hi = apr_hash_next(hi))
996 const char *relpath = apr_hash_this_key(hi);
999 if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
1001 else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
1003 /* When a path is already explicit deleted then its lock
1004 will be removed by mod_dav. But mod_dav doesn't remove
1005 locks on descendants */
1010 iterpool = svn_pool_create(pool);
1012 svn_pool_clear(iterpool);
1015 sb = svn_stringbuf_create("", pool);
1017 svn_stringbuf_appendbyte(sb, ' ');
1019 uri = commit_ctx->session->session_url;
1020 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
1023 svn_stringbuf_appendbyte(sb, '<');
1024 svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1025 svn_stringbuf_appendcstr(sb, "> (<");
1026 svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi));
1027 svn_stringbuf_appendcstr(sb, ">)");
1031 svn_pool_destroy(iterpool);
1035 serf_bucket_headers_set(headers, "If", sb->data);
1041 return SVN_NO_ERROR;
1044 static svn_error_t *
1045 setup_add_dir_common_headers(serf_bucket_t *headers,
1047 apr_pool_t *pool /* request pool */,
1048 apr_pool_t *scratch_pool)
1050 dir_context_t *dir = baton;
1051 svn_boolean_t added;
1053 return svn_error_trace(
1054 setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath,
1058 static svn_error_t *
1059 setup_copy_dir_headers(serf_bucket_t *headers,
1061 apr_pool_t *pool /* request pool */,
1062 apr_pool_t *scratch_pool)
1064 dir_context_t *dir = baton;
1066 const char *absolute_uri;
1068 /* The Dest URI must be absolute. Bummer. */
1069 uri = dir->commit_ctx->session->session_url;
1071 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1073 uri.path = (char *)dir->url;
1077 uri.path = (char *)svn_path_url_add_component2(
1078 dir->parent_dir->working_url,
1081 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1083 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1085 serf_bucket_headers_setn(headers, "Depth", "infinity");
1086 serf_bucket_headers_setn(headers, "Overwrite", "F");
1088 /* Implicitly checkout this dir now. */
1089 dir->working_url = apr_pstrdup(dir->pool, uri.path);
1091 return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool,
1095 static svn_error_t *
1096 setup_delete_headers(serf_bucket_t *headers,
1098 apr_pool_t *pool /* request pool */,
1099 apr_pool_t *scratch_pool)
1101 delete_context_t *del = baton;
1102 svn_boolean_t added;
1104 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1105 apr_ltoa(pool, del->revision));
1107 if (! del->non_recursive_if)
1108 SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx,
1109 del->relpath, pool));
1112 SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx,
1113 del->relpath, pool));
1117 if (added && del->commit_ctx->keep_locks)
1118 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1119 SVN_DAV_OPTION_KEEP_LOCKS);
1121 return SVN_NO_ERROR;
1124 /* POST against 'me' resource handlers. */
1126 /* Implements svn_ra_serf__request_body_delegate_t */
1127 static svn_error_t *
1128 create_txn_post_body(serf_bucket_t **body_bkt,
1130 serf_bucket_alloc_t *alloc,
1131 apr_pool_t *pool /* request pool */,
1132 apr_pool_t *scratch_pool)
1134 apr_hash_t *revprops = baton;
1135 svn_skel_t *request_skel;
1136 svn_stringbuf_t *skel_str;
1138 request_skel = svn_skel__make_empty_list(pool);
1141 svn_skel_t *proplist_skel;
1143 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1144 svn_skel__prepend(proplist_skel, request_skel);
1145 svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1146 skel_str = svn_skel__unparse(request_skel, pool);
1147 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1151 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1154 return SVN_NO_ERROR;
1157 /* Implements svn_ra_serf__request_header_delegate_t */
1158 static svn_error_t *
1159 setup_post_headers(serf_bucket_t *headers,
1161 apr_pool_t *pool /* request pool */,
1162 apr_pool_t *scratch_pool)
1164 #ifdef SVN_DAV_SEND_VTXN_NAME
1165 /* Enable this to exercise the VTXN-NAME code based on a client
1166 supplied transaction name. */
1167 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1168 svn_uuid_generate(pool));
1171 return SVN_NO_ERROR;
1175 /* Handler baton for POST request. */
1176 typedef struct post_response_ctx_t
1178 svn_ra_serf__handler_t *handler;
1179 commit_context_t *commit_ctx;
1180 } post_response_ctx_t;
1183 /* This implements serf_bucket_headers_do_callback_fn_t. */
1185 post_headers_iterator_callback(void *baton,
1189 post_response_ctx_t *prc = baton;
1190 commit_context_t *prc_cc = prc->commit_ctx;
1191 svn_ra_serf__session_t *sess = prc_cc->session;
1193 /* If we provided a UUID to the POST request, we should get back
1194 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1195 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
1198 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1200 /* Build out txn and txn-root URLs using the txn name we're
1201 given, and store the whole lot of it in the commit context. */
1203 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1204 prc_cc->txn_root_url =
1205 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1208 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1210 /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1211 given, and store the whole lot of it in the commit context. */
1213 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1214 prc_cc->txn_root_url =
1215 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1222 /* A custom serf_response_handler_t which is mostly a wrapper around
1223 svn_ra_serf__expect_empty_body -- it just notices POST response
1226 Implements svn_ra_serf__response_handler_t */
1227 static svn_error_t *
1228 post_response_handler(serf_request_t *request,
1229 serf_bucket_t *response,
1231 apr_pool_t *scratch_pool)
1233 post_response_ctx_t *prc = baton;
1234 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1236 /* Then see which ones we can discover. */
1237 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1239 /* Execute the 'real' response handler to XML-parse the repsonse body. */
1240 return svn_ra_serf__expect_empty_body(request, response,
1241 prc->handler, scratch_pool);
1246 /* Commit baton callbacks */
1248 static svn_error_t *
1249 open_root(void *edit_baton,
1250 svn_revnum_t base_revision,
1251 apr_pool_t *dir_pool,
1254 commit_context_t *commit_ctx = edit_baton;
1255 svn_ra_serf__handler_t *handler;
1256 proppatch_context_t *proppatch_ctx;
1258 apr_hash_index_t *hi;
1259 const char *proppatch_target = NULL;
1260 apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
1262 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
1264 post_response_ctx_t *prc;
1265 const char *rel_path;
1266 svn_boolean_t post_with_revprops
1267 = (NULL != svn_hash_gets(commit_ctx->session->supported_posts,
1268 "create-txn-with-props"));
1270 /* Create our activity URL now on the server. */
1271 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1273 handler->method = "POST";
1274 handler->body_type = SVN_SKEL_MIME_TYPE;
1275 handler->body_delegate = create_txn_post_body;
1276 handler->body_delegate_baton =
1277 post_with_revprops ? commit_ctx->revprop_table : NULL;
1278 handler->header_delegate = setup_post_headers;
1279 handler->header_delegate_baton = NULL;
1280 handler->path = commit_ctx->session->me_resource;
1282 prc = apr_pcalloc(scratch_pool, sizeof(*prc));
1283 prc->handler = handler;
1284 prc->commit_ctx = commit_ctx;
1286 handler->response_handler = post_response_handler;
1287 handler->response_baton = prc;
1289 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1291 if (handler->sline.code != 201)
1292 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1294 if (! (commit_ctx->txn_root_url && commit_ctx->txn_url))
1296 return svn_error_createf(
1297 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1298 _("POST request did not return transaction information"));
1301 /* Fixup the txn_root_url to point to the anchor of the commit. */
1302 SVN_ERR(svn_ra_serf__get_relative_path(
1304 commit_ctx->session->session_url.path,
1305 commit_ctx->session,
1307 commit_ctx->txn_root_url = svn_path_url_add_component2(
1308 commit_ctx->txn_root_url,
1309 rel_path, commit_ctx->pool);
1311 /* Build our directory baton. */
1312 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1313 dir->pool = dir_pool;
1314 dir->commit_ctx = commit_ctx;
1315 dir->base_revision = base_revision;
1318 dir->prop_changes = apr_hash_make(dir->pool);
1319 dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url);
1321 /* If we included our revprops in the POST, we need not
1323 proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url;
1327 const char *activity_str = commit_ctx->session->activity_collection_url;
1330 SVN_ERR(svn_ra_serf__v1_get_activity_collection(
1332 commit_ctx->session,
1333 scratch_pool, scratch_pool));
1335 commit_ctx->activity_url = svn_path_url_add_component2(
1337 svn_uuid_generate(scratch_pool),
1340 /* Create our activity URL now on the server. */
1341 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1343 handler->method = "MKACTIVITY";
1344 handler->path = commit_ctx->activity_url;
1346 handler->response_handler = svn_ra_serf__expect_empty_body;
1347 handler->response_baton = handler;
1349 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1351 if (handler->sline.code != 201)
1352 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1354 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1355 SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url),
1356 commit_ctx->session, scratch_pool));
1359 /* Build our directory baton. */
1360 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1361 dir->pool = dir_pool;
1362 dir->commit_ctx = commit_ctx;
1363 dir->base_revision = base_revision;
1366 dir->prop_changes = apr_hash_make(dir->pool);
1368 SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session,
1370 dir->base_revision, commit_ctx->checked_in_url,
1371 dir->pool, scratch_pool));
1372 commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url);
1374 /* Checkout our root dir */
1375 SVN_ERR(checkout_dir(dir, scratch_pool));
1377 proppatch_target = commit_ctx->baseline_url;
1380 /* Unless this is NULL -- which means we don't need to PROPPATCH the
1381 transaction with our revprops -- then, you know, PROPPATCH the
1382 transaction with our revprops. */
1383 if (proppatch_target)
1385 proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx));
1386 proppatch_ctx->pool = scratch_pool;
1387 proppatch_ctx->commit_ctx = NULL; /* No lock info */
1388 proppatch_ctx->path = proppatch_target;
1389 proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool);
1390 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1392 for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table);
1394 hi = apr_hash_next(hi))
1396 svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop));
1398 prop->name = apr_hash_this_key(hi);
1399 prop->value = apr_hash_this_val(hi);
1401 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
1404 SVN_ERR(proppatch_resource(commit_ctx->session,
1405 proppatch_ctx, scratch_pool));
1408 svn_pool_destroy(scratch_pool);
1412 return SVN_NO_ERROR;
1415 /* Implements svn_ra_serf__request_body_delegate_t */
1416 static svn_error_t *
1417 create_delete_body(serf_bucket_t **body_bkt,
1419 serf_bucket_alloc_t *alloc,
1420 apr_pool_t *pool /* request pool */,
1421 apr_pool_t *scratch_pool)
1423 delete_context_t *ctx = baton;
1424 serf_bucket_t *body;
1426 body = serf_bucket_aggregate_create(alloc);
1428 svn_ra_serf__add_xml_header_buckets(body, alloc);
1430 svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens,
1431 ctx->relpath, body, alloc, pool);
1434 return SVN_NO_ERROR;
1437 static svn_error_t *
1438 delete_entry(const char *path,
1439 svn_revnum_t revision,
1443 dir_context_t *dir = parent_baton;
1444 delete_context_t *delete_ctx;
1445 svn_ra_serf__handler_t *handler;
1446 const char *delete_target;
1448 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1450 delete_target = svn_path_url_add_component2(
1451 dir->commit_ctx->txn_root_url,
1456 /* Ensure our directory has been checked out */
1457 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1458 delete_target = svn_path_url_add_component2(dir->working_url,
1459 svn_relpath_basename(path,
1464 /* DELETE our entry */
1465 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1466 delete_ctx->relpath = apr_pstrdup(pool, path);
1467 delete_ctx->revision = revision;
1468 delete_ctx->commit_ctx = dir->commit_ctx;
1470 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1472 handler->response_handler = svn_ra_serf__expect_empty_body;
1473 handler->response_baton = handler;
1475 handler->header_delegate = setup_delete_headers;
1476 handler->header_delegate_baton = delete_ctx;
1478 handler->method = "DELETE";
1479 handler->path = delete_target;
1480 handler->no_fail_on_http_failure_status = TRUE;
1482 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1484 if (handler->sline.code == 400)
1486 /* Try again with non-standard body to overcome Apache Httpd
1488 delete_ctx->non_recursive_if = TRUE;
1490 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1492 handler->response_handler = svn_ra_serf__expect_empty_body;
1493 handler->response_baton = handler;
1495 handler->header_delegate = setup_delete_headers;
1496 handler->header_delegate_baton = delete_ctx;
1498 handler->method = "DELETE";
1499 handler->path = delete_target;
1501 handler->body_type = "text/xml";
1502 handler->body_delegate = create_delete_body;
1503 handler->body_delegate_baton = delete_ctx;
1505 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1508 if (handler->server_error)
1509 return svn_ra_serf__server_error_create(handler, pool);
1511 /* 204 No Content: item successfully deleted */
1512 if (handler->sline.code != 204)
1513 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1515 svn_hash_sets(dir->commit_ctx->deleted_entries,
1516 apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
1518 return SVN_NO_ERROR;
1521 static svn_error_t *
1522 add_directory(const char *path,
1524 const char *copyfrom_path,
1525 svn_revnum_t copyfrom_revision,
1526 apr_pool_t *dir_pool,
1529 dir_context_t *parent = parent_baton;
1531 svn_ra_serf__handler_t *handler;
1532 apr_status_t status;
1533 const char *mkcol_target;
1535 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1537 dir->pool = dir_pool;
1538 dir->parent_dir = parent;
1539 dir->commit_ctx = parent->commit_ctx;
1541 dir->base_revision = SVN_INVALID_REVNUM;
1542 dir->copy_revision = copyfrom_revision;
1543 dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1544 dir->relpath = apr_pstrdup(dir->pool, path);
1545 dir->name = svn_relpath_basename(dir->relpath, NULL);
1546 dir->prop_changes = apr_hash_make(dir->pool);
1548 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1550 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1552 mkcol_target = dir->url;
1556 /* Ensure our parent is checked out. */
1557 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1559 dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
1560 dir->name, dir->pool);
1561 mkcol_target = svn_path_url_add_component2(
1562 parent->working_url,
1563 dir->name, dir->pool);
1566 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
1568 handler->response_handler = svn_ra_serf__expect_empty_body;
1569 handler->response_baton = handler;
1570 if (!dir->copy_path)
1572 handler->method = "MKCOL";
1573 handler->path = mkcol_target;
1575 handler->header_delegate = setup_add_dir_common_headers;
1576 handler->header_delegate_baton = dir;
1581 const char *req_url;
1583 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1586 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1587 _("Unable to parse URL '%s'"),
1591 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1592 dir->commit_ctx->session,
1593 uri.path, dir->copy_revision,
1594 dir_pool, dir_pool));
1596 handler->method = "COPY";
1597 handler->path = req_url;
1599 handler->header_delegate = setup_copy_dir_headers;
1600 handler->header_delegate_baton = dir;
1602 /* We have the same problem as with DELETE here: if there are too many
1603 locks, the request fails. But in this case there is no way to retry
1604 with a non-standard request. #### How to fix? */
1605 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1607 if (handler->sline.code != 201)
1608 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1612 return SVN_NO_ERROR;
1615 static svn_error_t *
1616 open_directory(const char *path,
1618 svn_revnum_t base_revision,
1619 apr_pool_t *dir_pool,
1622 dir_context_t *parent = parent_baton;
1625 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1627 dir->pool = dir_pool;
1629 dir->parent_dir = parent;
1630 dir->commit_ctx = parent->commit_ctx;
1633 dir->base_revision = base_revision;
1634 dir->relpath = apr_pstrdup(dir->pool, path);
1635 dir->name = svn_relpath_basename(dir->relpath, NULL);
1636 dir->prop_changes = apr_hash_make(dir->pool);
1638 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1640 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1645 SVN_ERR(get_version_url(&dir->url,
1646 dir->commit_ctx->session,
1647 dir->relpath, dir->base_revision,
1648 dir->commit_ctx->checked_in_url,
1649 dir->pool, dir->pool /* scratch_pool */));
1653 return SVN_NO_ERROR;
1656 static svn_error_t *
1657 change_dir_prop(void *dir_baton,
1659 const svn_string_t *value,
1660 apr_pool_t *scratch_pool)
1662 dir_context_t *dir = dir_baton;
1665 if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1667 /* Ensure we have a checked out dir. */
1668 SVN_ERR(checkout_dir(dir, scratch_pool));
1671 prop = apr_palloc(dir->pool, sizeof(*prop));
1673 prop->name = apr_pstrdup(dir->pool, name);
1674 prop->value = svn_string_dup(value, dir->pool);
1676 svn_hash_sets(dir->prop_changes, prop->name, prop);
1678 return SVN_NO_ERROR;
1681 static svn_error_t *
1682 close_directory(void *dir_baton,
1685 dir_context_t *dir = dir_baton;
1687 /* Huh? We're going to be called before the texts are sent. Ugh.
1688 * Therefore, just wave politely at our caller.
1691 /* PROPPATCH our prop change and pass it along. */
1692 if (apr_hash_count(dir->prop_changes))
1694 proppatch_context_t *proppatch_ctx;
1696 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1697 proppatch_ctx->pool = pool;
1698 proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
1699 proppatch_ctx->relpath = dir->relpath;
1700 proppatch_ctx->prop_changes = dir->prop_changes;
1701 proppatch_ctx->base_revision = dir->base_revision;
1703 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1705 proppatch_ctx->path = dir->url;
1709 proppatch_ctx->path = dir->working_url;
1712 SVN_ERR(proppatch_resource(dir->commit_ctx->session,
1713 proppatch_ctx, dir->pool));
1716 return SVN_NO_ERROR;
1719 static svn_error_t *
1720 add_file(const char *path,
1722 const char *copy_path,
1723 svn_revnum_t copy_revision,
1724 apr_pool_t *file_pool,
1727 dir_context_t *dir = parent_baton;
1728 file_context_t *new_file;
1729 const char *deleted_parent = path;
1730 apr_pool_t *scratch_pool = svn_pool_create(file_pool);
1732 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1733 new_file->pool = file_pool;
1737 new_file->parent_dir = dir;
1738 new_file->commit_ctx = dir->commit_ctx;
1739 new_file->relpath = apr_pstrdup(new_file->pool, path);
1740 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1741 new_file->added = TRUE;
1742 new_file->base_revision = SVN_INVALID_REVNUM;
1743 new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1744 new_file->copy_revision = copy_revision;
1745 new_file->prop_changes = apr_hash_make(new_file->pool);
1747 /* Ensure that the file doesn't exist by doing a HEAD on the
1748 resource. If we're using HTTP v2, we'll just look into the
1749 transaction root tree for this thing. */
1750 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1752 new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
1753 path, new_file->pool);
1757 /* Ensure our parent directory has been checked out */
1758 SVN_ERR(checkout_dir(dir, scratch_pool));
1761 svn_path_url_add_component2(dir->working_url,
1762 new_file->name, new_file->pool);
1765 while (deleted_parent && deleted_parent[0] != '\0')
1767 if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
1771 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1776 svn_ra_serf__handler_t *handler;
1778 const char *req_url;
1779 apr_status_t status;
1781 /* Create the copy directly as cheap 'does exist/out of date'
1782 check. We update the copy (if needed) from close_file() */
1784 status = apr_uri_parse(scratch_pool, copy_path, &uri);
1786 return svn_ra_serf__wrap_err(status, NULL);
1788 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1789 dir->commit_ctx->session,
1790 uri.path, copy_revision,
1791 scratch_pool, scratch_pool));
1793 handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1795 handler->method = "COPY";
1796 handler->path = req_url;
1798 handler->response_handler = svn_ra_serf__expect_empty_body;
1799 handler->response_baton = handler;
1801 handler->header_delegate = setup_copy_file_headers;
1802 handler->header_delegate_baton = new_file;
1804 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1806 if (handler->sline.code != 201)
1807 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1809 else if (! ((dir->added && !dir->copy_path) ||
1810 (deleted_parent && deleted_parent[0] != '\0')))
1812 svn_ra_serf__handler_t *handler;
1815 handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1817 handler->method = "HEAD";
1818 handler->path = svn_path_url_add_component2(
1819 dir->commit_ctx->session->session_url.path,
1820 path, scratch_pool);
1821 handler->response_handler = svn_ra_serf__expect_empty_body;
1822 handler->response_baton = handler;
1823 handler->no_dav_headers = TRUE; /* Read only operation outside txn */
1825 err = svn_ra_serf__context_run_one(handler, scratch_pool);
1827 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1829 svn_error_clear(err); /* Great. We can create a new file! */
1832 return svn_error_trace(err);
1834 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1835 _("File '%s' already exists"), path);
1838 svn_pool_destroy(scratch_pool);
1839 *file_baton = new_file;
1841 return SVN_NO_ERROR;
1844 static svn_error_t *
1845 open_file(const char *path,
1847 svn_revnum_t base_revision,
1848 apr_pool_t *file_pool,
1851 dir_context_t *parent = parent_baton;
1852 file_context_t *new_file;
1854 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1855 new_file->pool = file_pool;
1857 parent->ref_count++;
1859 new_file->parent_dir = parent;
1860 new_file->commit_ctx = parent->commit_ctx;
1861 new_file->relpath = apr_pstrdup(new_file->pool, path);
1862 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1863 new_file->added = FALSE;
1864 new_file->base_revision = base_revision;
1865 new_file->prop_changes = apr_hash_make(new_file->pool);
1867 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
1869 new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1870 path, new_file->pool);
1874 /* CHECKOUT the file into our activity. */
1875 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1877 new_file->url = new_file->working_url;
1880 *file_baton = new_file;
1882 return SVN_NO_ERROR;
1885 /* Implements svn_stream_lazyopen_func_t for apply_textdelta */
1886 static svn_error_t *
1887 delayed_commit_stream_open(svn_stream_t **stream,
1889 apr_pool_t *result_pool,
1890 apr_pool_t *scratch_pool)
1892 file_context_t *file_ctx = baton;
1894 SVN_ERR(svn_io_open_unique_file3(&file_ctx->svndiff, NULL, NULL,
1895 svn_io_file_del_on_pool_cleanup,
1896 file_ctx->pool, scratch_pool));
1898 *stream = svn_stream_from_aprfile2(file_ctx->svndiff, TRUE, result_pool);
1899 return SVN_NO_ERROR;
1902 static svn_error_t *
1903 apply_textdelta(void *file_baton,
1904 const char *base_checksum,
1906 svn_txdelta_window_handler_t *handler,
1907 void **handler_baton)
1909 file_context_t *ctx = file_baton;
1911 /* Store the stream in a temporary file; we'll give it to serf when we
1914 * TODO: There should be a way we can stream the request body instead of
1915 * writing to a temporary file (ugh). A special svn stream serf bucket
1916 * that returns EAGAIN until we receive the done call? But, when
1917 * would we run through the serf context? Grr.
1920 ctx->stream = svn_stream_lazyopen_create(delayed_commit_stream_open,
1921 ctx, FALSE, ctx->pool);
1923 svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
1924 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
1927 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
1929 return SVN_NO_ERROR;
1932 static svn_error_t *
1933 change_file_prop(void *file_baton,
1935 const svn_string_t *value,
1938 file_context_t *file = file_baton;
1941 prop = apr_palloc(file->pool, sizeof(*prop));
1943 prop->name = apr_pstrdup(file->pool, name);
1944 prop->value = svn_string_dup(value, file->pool);
1946 svn_hash_sets(file->prop_changes, prop->name, prop);
1948 return SVN_NO_ERROR;
1951 static svn_error_t *
1952 close_file(void *file_baton,
1953 const char *text_checksum,
1954 apr_pool_t *scratch_pool)
1956 file_context_t *ctx = file_baton;
1957 svn_boolean_t put_empty_file = FALSE;
1959 ctx->result_checksum = text_checksum;
1961 /* If we got no stream of changes, but this is an added-without-history
1962 * file, make a note that we'll be PUTting a zero-byte file to the server.
1964 if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
1965 put_empty_file = TRUE;
1967 /* If we had a stream of changes, push them to the server... */
1968 if (ctx->svndiff || put_empty_file)
1970 svn_ra_serf__handler_t *handler;
1971 int expected_result;
1973 handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
1976 handler->method = "PUT";
1977 handler->path = ctx->url;
1979 handler->response_handler = svn_ra_serf__expect_empty_body;
1980 handler->response_baton = handler;
1984 handler->body_delegate = create_empty_put_body;
1985 handler->body_delegate_baton = ctx;
1986 handler->body_type = "text/plain";
1990 handler->body_delegate = create_put_body;
1991 handler->body_delegate_baton = ctx;
1992 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
1995 handler->header_delegate = setup_put_headers;
1996 handler->header_delegate_baton = ctx;
1998 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2000 if (ctx->added && ! ctx->copy_path)
2001 expected_result = 201; /* Created */
2003 expected_result = 204; /* Updated */
2005 if (handler->sline.code != expected_result)
2006 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2010 SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2012 /* If we had any prop changes, push them via PROPPATCH. */
2013 if (apr_hash_count(ctx->prop_changes))
2015 proppatch_context_t *proppatch;
2017 proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
2018 proppatch->pool = scratch_pool;
2019 proppatch->relpath = ctx->relpath;
2020 proppatch->path = ctx->url;
2021 proppatch->commit_ctx = ctx->commit_ctx;
2022 proppatch->prop_changes = ctx->prop_changes;
2023 proppatch->base_revision = ctx->base_revision;
2025 SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
2026 proppatch, scratch_pool));
2029 return SVN_NO_ERROR;
2032 static svn_error_t *
2033 close_edit(void *edit_baton,
2036 commit_context_t *ctx = edit_baton;
2037 const char *merge_target =
2038 ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2039 const svn_commit_info_t *commit_info;
2040 svn_error_t *err = NULL;
2042 /* MERGE our activity */
2043 SVN_ERR(svn_ra_serf__run_merge(&commit_info,
2050 ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
2052 /* Inform the WC that we did a commit. */
2054 err = ctx->callback(commit_info, ctx->callback_baton, pool);
2056 /* If we're using activities, DELETE our completed activity. */
2057 if (ctx->activity_url)
2059 svn_ra_serf__handler_t *handler;
2061 handler = svn_ra_serf__create_handler(ctx->session, pool);
2063 handler->method = "DELETE";
2064 handler->path = ctx->activity_url;
2066 handler->response_handler = svn_ra_serf__expect_empty_body;
2067 handler->response_baton = handler;
2069 ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
2071 SVN_ERR(svn_error_compose_create(
2073 svn_ra_serf__context_run_one(handler, pool)));
2075 if (handler->sline.code != 204)
2076 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2081 return SVN_NO_ERROR;
2084 static svn_error_t *
2085 abort_edit(void *edit_baton,
2088 commit_context_t *ctx = edit_baton;
2089 svn_ra_serf__handler_t *handler;
2091 /* If an activity or transaction wasn't even created, don't bother
2092 trying to delete it. */
2093 if (! (ctx->activity_url || ctx->txn_url))
2094 return SVN_NO_ERROR;
2096 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2097 had a problem. We need to reset it, in order to use it again. */
2098 serf_connection_reset(ctx->session->conns[0]->conn);
2100 /* DELETE our aborted activity */
2101 handler = svn_ra_serf__create_handler(ctx->session, pool);
2103 handler->method = "DELETE";
2105 handler->response_handler = svn_ra_serf__expect_empty_body;
2106 handler->response_baton = handler;
2107 handler->no_fail_on_http_failure_status = TRUE;
2109 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2110 handler->path = ctx->txn_url;
2112 handler->path = ctx->activity_url;
2114 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2117 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2118 404 if the activity wasn't found. */
2119 if (handler->sline.code != 204
2120 && handler->sline.code != 403
2121 && handler->sline.code != 404)
2123 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2126 /* Don't delete again if somebody aborts twice */
2127 ctx->activity_url = NULL;
2128 ctx->txn_url = NULL;
2130 return SVN_NO_ERROR;
2134 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2135 const svn_delta_editor_t **ret_editor,
2137 apr_hash_t *revprop_table,
2138 svn_commit_callback2_t callback,
2139 void *callback_baton,
2140 apr_hash_t *lock_tokens,
2141 svn_boolean_t keep_locks,
2144 svn_ra_serf__session_t *session = ra_session->priv;
2145 svn_delta_editor_t *editor;
2146 commit_context_t *ctx;
2147 const char *repos_root;
2148 const char *base_relpath;
2149 svn_boolean_t supports_ephemeral_props;
2151 ctx = apr_pcalloc(pool, sizeof(*ctx));
2155 ctx->session = session;
2157 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2159 /* If the server supports ephemeral properties, add some carrying
2160 interesting version information. */
2161 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2162 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2164 if (supports_ephemeral_props)
2166 svn_hash_sets(ctx->revprop_table,
2167 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2168 svn_string_create(SVN_VER_NUMBER, pool));
2169 svn_hash_sets(ctx->revprop_table,
2170 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2171 svn_string_create(session->useragent, pool));
2174 ctx->callback = callback;
2175 ctx->callback_baton = callback_baton;
2177 ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2178 ? lock_tokens : NULL;
2179 ctx->keep_locks = keep_locks;
2181 ctx->deleted_entries = apr_hash_make(ctx->pool);
2183 editor = svn_delta_default_editor(pool);
2184 editor->open_root = open_root;
2185 editor->delete_entry = delete_entry;
2186 editor->add_directory = add_directory;
2187 editor->open_directory = open_directory;
2188 editor->change_dir_prop = change_dir_prop;
2189 editor->close_directory = close_directory;
2190 editor->add_file = add_file;
2191 editor->open_file = open_file;
2192 editor->apply_textdelta = apply_textdelta;
2193 editor->change_file_prop = change_file_prop;
2194 editor->close_file = close_file;
2195 editor->close_edit = close_edit;
2196 editor->abort_edit = abort_edit;
2198 *ret_editor = editor;
2201 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2202 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2205 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2206 *edit_baton, repos_root, base_relpath,
2207 session->shim_callbacks, pool, pool));
2209 return SVN_NO_ERROR;
2213 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2216 const svn_string_t *const *old_value_p,
2217 const svn_string_t *value,
2220 svn_ra_serf__session_t *session = ra_session->priv;
2221 proppatch_context_t *proppatch_ctx;
2222 const char *proppatch_target;
2223 const svn_string_t *tmp_old_value;
2224 svn_boolean_t atomic_capable = FALSE;
2228 if (old_value_p || !value)
2229 SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable,
2230 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2235 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2236 SVN_ERR_ASSERT(atomic_capable);
2238 else if (! value && atomic_capable)
2240 svn_string_t *old_value;
2241 /* mod_dav_svn doesn't report a failure when a property delete fails. The
2242 atomic revprop change behavior is a nice workaround, to allow getting
2243 access to the error anyway.
2245 Somehow the mod_dav maintainers think that returning an error from
2246 mod_dav's property delete is an RFC violation.
2247 See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */
2249 SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value,
2253 return SVN_NO_ERROR; /* Nothing to delete */
2255 /* The api expects a double const pointer. Let's make one */
2256 tmp_old_value = old_value;
2257 old_value_p = &tmp_old_value;
2260 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2262 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2266 const char *vcc_url;
2268 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
2270 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2271 session, vcc_url, rev, "href",
2275 /* PROPPATCH our log message and pass it along. */
2276 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2277 proppatch_ctx->pool = pool;
2278 proppatch_ctx->commit_ctx = NULL; /* No lock headers */
2279 proppatch_ctx->path = proppatch_target;
2280 proppatch_ctx->prop_changes = apr_hash_make(pool);
2281 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2285 prop = apr_palloc(pool, sizeof (*prop));
2288 prop->value = *old_value_p;
2290 proppatch_ctx->old_props = apr_hash_make(pool);
2291 svn_hash_sets(proppatch_ctx->old_props, prop->name, prop);
2294 prop = apr_palloc(pool, sizeof (*prop));
2297 prop->value = value;
2298 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
2300 err = proppatch_resource(session, proppatch_ctx, pool);
2302 /* Use specific error code for old property value mismatch.
2303 Use loop to provide the right result with tracing */
2304 if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2306 svn_error_t *e = err;
2308 while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2310 e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
2315 return svn_error_trace(err);