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;
54 svn_ra_serf__connection_t *conn;
56 apr_hash_t *revprop_table;
58 svn_commit_callback2_t callback;
61 apr_hash_t *lock_tokens;
62 svn_boolean_t keep_locks;
63 apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */
66 const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */
67 const char *txn_root_url; /* commit anchor txn root URL */
69 /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
70 const char *activity_url; /* activity base URL... */
71 const char *baseline_url; /* the working-baseline resource */
72 const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
73 const char *vcc_url; /* vcc url */
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;
88 /* Changed and removed properties. */
89 apr_hash_t *changed_props;
90 apr_hash_t *removed_props;
92 /* Same, for the old value (*old_value_p). */
93 apr_hash_t *previous_changed_props;
94 apr_hash_t *previous_removed_props;
96 /* In HTTP v2, this is the file/directory version we think we're changing. */
97 svn_revnum_t base_revision;
99 } proppatch_context_t;
101 typedef struct delete_context_t {
104 svn_revnum_t revision;
106 const char *lock_token;
107 apr_hash_t *lock_token_hash;
108 svn_boolean_t keep_locks;
112 /* Represents a directory. */
113 typedef struct dir_context_t {
114 /* Pool for our directory. */
117 /* The root commit we're in progress for. */
118 commit_context_t *commit;
120 /* URL to operate against (used for CHECKOUT and PROPPATCH before
121 HTTP v2, for PROPPATCH in HTTP v2). */
124 /* How many pending changes we have left in this directory. */
125 unsigned int ref_count;
127 /* Is this directory being added? (Otherwise, just opened.) */
131 struct dir_context_t *parent_dir;
133 /* The directory name; if "", we're the 'root' */
136 /* The basename of the directory. "" for the 'root' */
139 /* The base revision of the dir. */
140 svn_revnum_t base_revision;
142 const char *copy_path;
143 svn_revnum_t copy_revision;
145 /* Changed and removed properties */
146 apr_hash_t *changed_props;
147 apr_hash_t *removed_props;
149 /* The checked-out working resource for this directory. May be NULL; if so
150 call checkout_dir() first. */
151 const char *working_url;
155 /* Represents a file to be committed. */
156 typedef struct file_context_t {
157 /* Pool for our file. */
160 /* The root commit we're in progress for. */
161 commit_context_t *commit;
163 /* Is this file being added? (Otherwise, just opened.) */
166 dir_context_t *parent_dir;
171 /* The checked-out working resource for this file. */
172 const char *working_url;
174 /* The base revision of the file. */
175 svn_revnum_t base_revision;
177 /* Copy path and revision */
178 const char *copy_path;
179 svn_revnum_t copy_revision;
182 svn_stream_t *stream;
184 /* Temporary file containing the svndiff. */
187 /* Our base checksum as reported by the WC. */
188 const char *base_checksum;
190 /* Our resulting checksum as reported by the WC. */
191 const char *result_checksum;
193 /* Changed and removed properties. */
194 apr_hash_t *changed_props;
195 apr_hash_t *removed_props;
197 /* URL to PUT the file at. */
203 /* Setup routines and handlers for various requests we'll invoke. */
206 return_response_err(svn_ra_serf__handler_t *handler)
210 /* We should have captured SLINE and LOCATION in the HANDLER. */
211 SVN_ERR_ASSERT(handler->handler_pool != NULL);
213 /* Ye Olde Fallback Error */
214 err = svn_error_compose_create(
215 handler->server_error != NULL
216 ? handler->server_error->error
218 svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
219 _("%s of '%s': %d %s"),
220 handler->method, handler->path,
221 handler->sline.code, handler->sline.reason));
223 /* Try to return one of the standard errors for 301, 404, etc.,
224 then look for an error embedded in the response. */
225 return svn_error_compose_create(svn_ra_serf__error_on_status(
232 /* Implements svn_ra_serf__request_body_delegate_t */
234 create_checkout_body(serf_bucket_t **bkt,
236 serf_bucket_alloc_t *alloc,
239 const char *activity_url = baton;
240 serf_bucket_t *body_bkt;
242 body_bkt = serf_bucket_aggregate_create(alloc);
244 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
245 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
248 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
249 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
251 SVN_ERR_ASSERT(activity_url != NULL);
252 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
254 strlen(activity_url));
256 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
257 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
258 svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc);
259 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
266 /* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
267 given COMMIT_CTX. The resulting working resource will be returned in
268 *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
269 are performed in SCRATCH_POOL.
271 ### are these URLs actually repos relpath values? or fspath? or maybe
272 ### the abspath portion of the full URL.
274 This function operates synchronously.
276 Strictly speaking, we could perform "all" of the CHECKOUT requests
277 when the commit starts, and only block when we need a specific
278 answer. Or, at a minimum, send off these individual requests async
279 and block when we need the answer (eg PUT or PROPPATCH).
281 However: the investment to speed this up is not worthwhile, given
282 that CHECKOUT (and the related round trip) is completely obviated
286 checkout_node(const char **working_url,
287 const commit_context_t *commit_ctx,
288 const char *node_url,
289 apr_pool_t *result_pool,
290 apr_pool_t *scratch_pool)
292 svn_ra_serf__handler_t handler = { 0 };
296 /* HANDLER_POOL is the scratch pool since we don't need to remember
297 anything from the handler. We just want the working resource. */
298 handler.handler_pool = scratch_pool;
299 handler.session = commit_ctx->session;
300 handler.conn = commit_ctx->conn;
302 handler.body_delegate = create_checkout_body;
303 handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
304 handler.body_type = "text/xml";
306 handler.response_handler = svn_ra_serf__expect_empty_body;
307 handler.response_baton = &handler;
309 handler.method = "CHECKOUT";
310 handler.path = node_url;
312 SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
314 if (handler.sline.code != 201)
315 return svn_error_trace(return_response_err(&handler));
317 if (handler.location == NULL)
318 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
319 _("No Location header received"));
321 /* We only want the path portion of the Location header.
322 (code.google.com sometimes returns an 'http:' scheme for an
323 'https:' transaction ... we'll work around that by stripping the
324 scheme, host, and port here and re-adding the correct ones
326 status = apr_uri_parse(scratch_pool, handler.location, &uri);
328 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
329 _("Error parsing Location header value"));
331 *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
337 /* This is a wrapper around checkout_node() (which see for
338 documentation) which simply retries the CHECKOUT request when it
339 fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
342 See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
346 retry_checkout_node(const char **working_url,
347 const commit_context_t *commit_ctx,
348 const char *node_url,
349 apr_pool_t *result_pool,
350 apr_pool_t *scratch_pool)
352 svn_error_t *err = SVN_NO_ERROR;
353 int retry_count = 5; /* Magic, arbitrary number. */
357 svn_error_clear(err);
359 err = checkout_node(working_url, commit_ctx, node_url,
360 result_pool, scratch_pool);
362 /* There's a small chance of a race condition here if Apache is
363 experiencing heavy commit concurrency or if the network has
364 long latency. It's possible that the value of HEAD changed
365 between the time we fetched the latest baseline and the time
366 we try to CHECKOUT that baseline. If that happens, Apache
367 will throw us a BAD_BASELINE error (deltaV says you can only
368 checkout the latest baseline). We just ignore that specific
369 error and retry a few times, asking for the latest baseline
371 if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
374 while (err && retry_count--);
381 checkout_dir(dir_context_t *dir,
382 apr_pool_t *scratch_pool)
385 dir_context_t *p_dir = dir;
386 const char *checkout_url;
387 const char **working;
389 if (dir->working_url)
394 /* Is this directory or one of our parent dirs newly added?
395 * If so, we're already implicitly checked out. */
400 /* Implicitly checkout this dir now. */
401 dir->working_url = svn_path_url_add_component2(
402 dir->parent_dir->working_url,
403 dir->name, dir->pool);
406 p_dir = p_dir->parent_dir;
409 /* We could be called twice for the root: once to checkout the baseline;
410 * once to checkout the directory itself if we need to do so.
411 * Note: CHECKOUT_URL should live longer than HANDLER.
413 if (!dir->parent_dir && !dir->commit->baseline_url)
415 checkout_url = dir->commit->vcc_url;
416 working = &dir->commit->baseline_url;
420 checkout_url = dir->url;
421 working = &dir->working_url;
424 /* Checkout our directory into the activity URL now. */
425 err = retry_checkout_node(working, dir->commit, checkout_url,
426 dir->pool, scratch_pool);
429 if (err->apr_err == SVN_ERR_FS_CONFLICT)
430 SVN_ERR_W(err, apr_psprintf(scratch_pool,
431 _("Directory '%s' is out of date; try updating"),
432 svn_dirent_local_style(dir->relpath, scratch_pool)));
440 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
441 * RELPATH (relative to the root of SESSION).
443 * Try to find this version url in three ways:
444 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
445 * version url from the working copy properties.
446 * Second, if the version url of the parent directory PARENT_VSN_URL is
447 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
449 * Else, fetch the version url for the root of SESSION using CONN and
450 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
453 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
454 * temporary allocation.
457 get_version_url(const char **checked_in_url,
458 svn_ra_serf__session_t *session,
460 svn_revnum_t base_revision,
461 const char *parent_vsn_url,
462 apr_pool_t *result_pool,
463 apr_pool_t *scratch_pool)
465 const char *root_checkout;
467 if (session->wc_callbacks->get_wc_prop)
469 const svn_string_t *current_version;
471 SVN_ERR(session->wc_callbacks->get_wc_prop(
472 session->wc_callback_baton,
474 SVN_RA_SERF__WC_CHECKED_IN_URL,
475 ¤t_version, scratch_pool));
480 svn_urlpath__canonicalize(current_version->data, result_pool);
487 root_checkout = parent_vsn_url;
491 const char *propfind_url;
492 svn_ra_serf__connection_t *conn = session->conns[0];
494 if (SVN_IS_VALID_REVNUM(base_revision))
496 /* mod_dav_svn can't handle the "Label:" header that
497 svn_ra_serf__deliver_props() is going to try to use for
498 this lookup, so we'll do things the hard(er) way, by
499 looking up the version URL from a resource in the
500 baseline collection. */
501 /* ### conn==NULL for session->conns[0]. same as CONN. */
502 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
503 NULL /* latest_revnum */,
504 session, NULL /* conn */,
505 NULL /* url */, base_revision,
506 scratch_pool, scratch_pool));
510 propfind_url = session->session_url.path;
513 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
514 conn, propfind_url, base_revision,
516 scratch_pool, scratch_pool));
518 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
519 _("Path '%s' not present"),
520 session->session_url.path);
522 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
525 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
532 checkout_file(file_context_t *file,
533 apr_pool_t *scratch_pool)
536 dir_context_t *parent_dir = file->parent_dir;
537 const char *checkout_url;
539 /* Is one of our parent dirs newly added? If so, we're already
540 * implicitly checked out.
544 if (parent_dir->added)
546 /* Implicitly checkout this file now. */
547 file->working_url = svn_path_url_add_component2(
548 parent_dir->working_url,
549 svn_relpath_skip_ancestor(
550 parent_dir->relpath, file->relpath),
554 parent_dir = parent_dir->parent_dir;
557 SVN_ERR(get_version_url(&checkout_url,
558 file->commit->session,
559 file->relpath, file->base_revision,
560 NULL, scratch_pool, scratch_pool));
562 /* Checkout our file into the activity URL now. */
563 err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
564 file->pool, scratch_pool);
567 if (err->apr_err == SVN_ERR_FS_CONFLICT)
568 SVN_ERR_W(err, apr_psprintf(scratch_pool,
569 _("File '%s' is out of date; try updating"),
570 svn_dirent_local_style(file->relpath, scratch_pool)));
577 /* Helper function for proppatch_walker() below. */
579 get_encoding_and_cdata(const char **encoding_p,
580 const svn_string_t **encoded_value_p,
581 serf_bucket_alloc_t *alloc,
582 const svn_string_t *value,
583 apr_pool_t *result_pool,
584 apr_pool_t *scratch_pool)
589 *encoded_value_p = NULL;
593 /* If a property is XML-safe, XML-encode it. Else, base64-encode
595 if (svn_xml_is_xml_safe(value->data, value->len))
597 svn_stringbuf_t *xml_esc = NULL;
598 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
600 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
604 *encoding_p = "base64";
605 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
611 typedef struct walker_baton_t {
612 serf_bucket_t *body_bkt;
613 apr_pool_t *body_pool;
615 apr_hash_t *previous_changed_props;
616 apr_hash_t *previous_removed_props;
620 /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
621 rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */
624 filter_props_with_old_value,
625 filter_props_without_old_value
628 /* Is the property being deleted? */
629 svn_boolean_t deleting;
632 /* If we have (recorded in WB) the old value of the property named NS:NAME,
633 * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
634 * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */
636 derive_old_val(svn_boolean_t *have_old_val,
637 const svn_string_t **old_val_p,
642 *have_old_val = FALSE;
644 if (wb->previous_changed_props)
646 const svn_string_t *val;
647 val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
651 *have_old_val = TRUE;
656 if (wb->previous_removed_props)
658 const svn_string_t *val;
659 val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
663 *have_old_val = TRUE;
672 proppatch_walker(void *baton,
675 const svn_string_t *val,
676 apr_pool_t *scratch_pool)
678 walker_baton_t *wb = baton;
679 serf_bucket_t *body_bkt = wb->body_bkt;
680 serf_bucket_t *cdata_bkt;
681 serf_bucket_alloc_t *alloc;
682 const char *encoding;
683 svn_boolean_t have_old_val;
684 const svn_string_t *old_val;
685 const svn_string_t *encoded_value;
686 const char *prop_name;
688 SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
690 /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
692 if (wb->filter != filter_all_props)
694 if (wb->filter == filter_props_with_old_value && ! have_old_val)
696 if (wb->filter == filter_props_without_old_value && have_old_val)
702 alloc = body_bkt->allocator;
704 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
705 wb->body_pool, scratch_pool));
708 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
717 /* Use the namespace prefix instead of adding the xmlns attribute to support
718 property names containing ':' */
719 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
720 prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
721 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
722 prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
725 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
726 "V:encoding", encoding,
729 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
730 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
735 const char *encoding2;
736 const svn_string_t *encoded_value2;
737 serf_bucket_t *cdata_bkt2;
739 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
741 wb->body_pool, scratch_pool));
745 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
755 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
756 "V:" SVN_DAV__OLD_VALUE,
757 "V:encoding", encoding2,
760 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
761 "V:" SVN_DAV__OLD_VALUE,
762 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
766 serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
768 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
769 "V:" SVN_DAV__OLD_VALUE);
772 serf_bucket_aggregate_append(body_bkt, cdata_bkt);
773 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
778 /* Possible add the lock-token "If:" precondition header to HEADERS if
779 an examination of COMMIT_CTX and RELPATH indicates that this is the
782 Generally speaking, if the client provided a lock token for
783 RELPATH, it's the right thing to do. There is a notable instance
784 where this is not the case, however. If the file at RELPATH was
785 explicitly deleted in this commit already, then mod_dav removed its
786 lock token when it fielded the DELETE request, so we don't want to
787 set the lock precondition again. (See
788 http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
791 maybe_set_lock_token_header(serf_bucket_t *headers,
792 commit_context_t *commit_ctx,
798 if (! (relpath && commit_ctx->lock_tokens))
801 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
803 token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
806 const char *token_header;
807 const char *token_uri;
808 apr_uri_t uri = commit_ctx->session->session_url;
810 /* Supplying the optional URI affects apache response when
811 the lock is broken, see issue 4369. When present any URI
812 must be absolute (RFC 2518 9.4). */
813 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
815 token_uri = apr_uri_unparse(pool, &uri, 0);
817 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
819 serf_bucket_headers_set(headers, "If", token_header);
827 setup_proppatch_headers(serf_bucket_t *headers,
831 proppatch_context_t *proppatch = baton;
833 if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
835 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
836 apr_psprintf(pool, "%ld",
837 proppatch->base_revision));
840 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
841 proppatch->relpath, pool));
847 struct proppatch_body_baton_t {
848 proppatch_context_t *proppatch;
850 /* Content in the body should be allocated here, to live long enough. */
851 apr_pool_t *body_pool;
854 /* Implements svn_ra_serf__request_body_delegate_t */
856 create_proppatch_body(serf_bucket_t **bkt,
858 serf_bucket_alloc_t *alloc,
859 apr_pool_t *scratch_pool)
861 struct proppatch_body_baton_t *pbb = baton;
862 proppatch_context_t *ctx = pbb->proppatch;
863 serf_bucket_t *body_bkt;
864 walker_baton_t wb = { 0 };
866 body_bkt = serf_bucket_aggregate_create(alloc);
868 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
869 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
871 "xmlns:V", SVN_DAV_PROP_NS_DAV,
872 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
873 "xmlns:S", SVN_DAV_PROP_NS_SVN,
876 wb.body_bkt = body_bkt;
877 wb.body_pool = pbb->body_pool;
878 wb.previous_changed_props = ctx->previous_changed_props;
879 wb.previous_removed_props = ctx->previous_removed_props;
882 if (apr_hash_count(ctx->changed_props) > 0)
884 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
885 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
887 wb.filter = filter_all_props;
889 SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
891 proppatch_walker, &wb,
894 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
895 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
898 if (apr_hash_count(ctx->removed_props) > 0)
900 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
901 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
903 wb.filter = filter_props_with_old_value;
905 SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
907 proppatch_walker, &wb,
910 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
911 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
914 if (apr_hash_count(ctx->removed_props) > 0)
916 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
917 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
919 wb.filter = filter_props_without_old_value;
921 SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
923 proppatch_walker, &wb,
926 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
927 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
930 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
937 proppatch_resource(proppatch_context_t *proppatch,
938 commit_context_t *commit,
941 svn_ra_serf__handler_t *handler;
942 struct proppatch_body_baton_t pbb;
944 handler = apr_pcalloc(pool, sizeof(*handler));
945 handler->handler_pool = pool;
946 handler->method = "PROPPATCH";
947 handler->path = proppatch->path;
948 handler->conn = commit->conn;
949 handler->session = commit->session;
951 handler->header_delegate = setup_proppatch_headers;
952 handler->header_delegate_baton = proppatch;
954 pbb.proppatch = proppatch;
955 pbb.body_pool = pool;
956 handler->body_delegate = create_proppatch_body;
957 handler->body_delegate_baton = &pbb;
959 handler->response_handler = svn_ra_serf__handle_multistatus_only;
960 handler->response_baton = handler;
962 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
964 if (handler->sline.code != 207
965 || (handler->server_error != NULL
966 && handler->server_error->error != NULL))
968 return svn_error_create(
969 SVN_ERR_RA_DAV_PROPPATCH_FAILED,
970 return_response_err(handler),
971 _("At least one property change failed; repository"
978 /* Implements svn_ra_serf__request_body_delegate_t */
980 create_put_body(serf_bucket_t **body_bkt,
982 serf_bucket_alloc_t *alloc,
985 file_context_t *ctx = baton;
988 /* We need to flush the file, make it unbuffered (so that it can be
989 * zero-copied via mmap), and reset the position before attempting to
992 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
993 * and zero-copy the PUT body. However, on older APR versions, we can't
994 * check the buffer status; but serf will fall through and create a file
995 * bucket for us on the buffered svndiff handle.
997 apr_file_flush(ctx->svndiff);
998 #if APR_VERSION_AT_LEAST(1, 3, 0)
999 apr_file_buffer_set(ctx->svndiff, NULL, 0);
1002 apr_file_seek(ctx->svndiff, APR_SET, &offset);
1004 *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
1005 return SVN_NO_ERROR;
1008 /* Implements svn_ra_serf__request_body_delegate_t */
1009 static svn_error_t *
1010 create_empty_put_body(serf_bucket_t **body_bkt,
1012 serf_bucket_alloc_t *alloc,
1015 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
1016 return SVN_NO_ERROR;
1019 static svn_error_t *
1020 setup_put_headers(serf_bucket_t *headers,
1024 file_context_t *ctx = baton;
1026 if (SVN_IS_VALID_REVNUM(ctx->base_revision))
1028 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1029 apr_psprintf(pool, "%ld", ctx->base_revision));
1032 if (ctx->base_checksum)
1034 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
1035 ctx->base_checksum);
1038 if (ctx->result_checksum)
1040 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
1041 ctx->result_checksum);
1044 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
1045 ctx->relpath, pool));
1050 static svn_error_t *
1051 setup_copy_file_headers(serf_bucket_t *headers,
1055 file_context_t *file = baton;
1057 const char *absolute_uri;
1059 /* The Dest URI must be absolute. Bummer. */
1060 uri = file->commit->session->session_url;
1061 uri.path = (char*)file->url;
1062 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1064 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1066 serf_bucket_headers_setn(headers, "Depth", "0");
1067 serf_bucket_headers_setn(headers, "Overwrite", "T");
1069 return SVN_NO_ERROR;
1072 static svn_error_t *
1073 setup_copy_dir_headers(serf_bucket_t *headers,
1077 dir_context_t *dir = baton;
1079 const char *absolute_uri;
1081 /* The Dest URI must be absolute. Bummer. */
1082 uri = dir->commit->session->session_url;
1084 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1086 uri.path = (char *)dir->url;
1090 uri.path = (char *)svn_path_url_add_component2(
1091 dir->parent_dir->working_url,
1094 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1096 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1098 serf_bucket_headers_setn(headers, "Depth", "infinity");
1099 serf_bucket_headers_setn(headers, "Overwrite", "T");
1101 /* Implicitly checkout this dir now. */
1102 dir->working_url = apr_pstrdup(dir->pool, uri.path);
1104 return SVN_NO_ERROR;
1107 static svn_error_t *
1108 setup_delete_headers(serf_bucket_t *headers,
1112 delete_context_t *ctx = baton;
1114 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1115 apr_ltoa(pool, ctx->revision));
1117 if (ctx->lock_token_hash)
1119 ctx->lock_token = svn_hash_gets(ctx->lock_token_hash, ctx->path);
1121 if (ctx->lock_token)
1123 const char *token_header;
1125 token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
1126 ctx->lock_token, ">)", (char *)NULL);
1128 serf_bucket_headers_set(headers, "If", token_header);
1130 if (ctx->keep_locks)
1131 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1132 SVN_DAV_OPTION_KEEP_LOCKS);
1136 return SVN_NO_ERROR;
1139 /* Implements svn_ra_serf__request_body_delegate_t */
1140 static svn_error_t *
1141 create_delete_body(serf_bucket_t **body_bkt,
1143 serf_bucket_alloc_t *alloc,
1146 delete_context_t *ctx = baton;
1147 serf_bucket_t *body;
1149 body = serf_bucket_aggregate_create(alloc);
1151 svn_ra_serf__add_xml_header_buckets(body, alloc);
1153 svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
1157 return SVN_NO_ERROR;
1160 /* Helper function to write the svndiff stream to temporary file. */
1161 static svn_error_t *
1162 svndiff_stream_write(void *file_baton,
1166 file_context_t *ctx = file_baton;
1167 apr_status_t status;
1169 status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
1171 return svn_error_wrap_apr(status, _("Failed writing updated file"));
1173 return SVN_NO_ERROR;
1178 /* POST against 'me' resource handlers. */
1180 /* Implements svn_ra_serf__request_body_delegate_t */
1181 static svn_error_t *
1182 create_txn_post_body(serf_bucket_t **body_bkt,
1184 serf_bucket_alloc_t *alloc,
1187 apr_hash_t *revprops = baton;
1188 svn_skel_t *request_skel;
1189 svn_stringbuf_t *skel_str;
1191 request_skel = svn_skel__make_empty_list(pool);
1194 svn_skel_t *proplist_skel;
1196 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1197 svn_skel__prepend(proplist_skel, request_skel);
1198 svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1199 skel_str = svn_skel__unparse(request_skel, pool);
1200 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1204 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1207 return SVN_NO_ERROR;
1210 /* Implements svn_ra_serf__request_header_delegate_t */
1211 static svn_error_t *
1212 setup_post_headers(serf_bucket_t *headers,
1216 #ifdef SVN_DAV_SEND_VTXN_NAME
1217 /* Enable this to exercise the VTXN-NAME code based on a client
1218 supplied transaction name. */
1219 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1220 svn_uuid_generate(pool));
1223 return SVN_NO_ERROR;
1227 /* Handler baton for POST request. */
1228 typedef struct post_response_ctx_t
1230 svn_ra_serf__handler_t *handler;
1231 commit_context_t *commit_ctx;
1232 } post_response_ctx_t;
1235 /* This implements serf_bucket_headers_do_callback_fn_t. */
1237 post_headers_iterator_callback(void *baton,
1241 post_response_ctx_t *prc = baton;
1242 commit_context_t *prc_cc = prc->commit_ctx;
1243 svn_ra_serf__session_t *sess = prc_cc->session;
1245 /* If we provided a UUID to the POST request, we should get back
1246 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1247 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
1250 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1252 /* Build out txn and txn-root URLs using the txn name we're
1253 given, and store the whole lot of it in the commit context. */
1255 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1256 prc_cc->txn_root_url =
1257 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1260 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1262 /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1263 given, and store the whole lot of it in the commit context. */
1265 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1266 prc_cc->txn_root_url =
1267 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1274 /* A custom serf_response_handler_t which is mostly a wrapper around
1275 svn_ra_serf__expect_empty_body -- it just notices POST response
1278 Implements svn_ra_serf__response_handler_t */
1279 static svn_error_t *
1280 post_response_handler(serf_request_t *request,
1281 serf_bucket_t *response,
1283 apr_pool_t *scratch_pool)
1285 post_response_ctx_t *prc = baton;
1286 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1288 /* Then see which ones we can discover. */
1289 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1291 /* Execute the 'real' response handler to XML-parse the repsonse body. */
1292 return svn_ra_serf__expect_empty_body(request, response,
1293 prc->handler, scratch_pool);
1298 /* Commit baton callbacks */
1300 static svn_error_t *
1301 open_root(void *edit_baton,
1302 svn_revnum_t base_revision,
1303 apr_pool_t *dir_pool,
1306 commit_context_t *ctx = edit_baton;
1307 svn_ra_serf__handler_t *handler;
1308 proppatch_context_t *proppatch_ctx;
1310 apr_hash_index_t *hi;
1311 const char *proppatch_target = NULL;
1313 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
1315 post_response_ctx_t *prc;
1316 const char *rel_path;
1317 svn_boolean_t post_with_revprops
1318 = (NULL != svn_hash_gets(ctx->session->supported_posts,
1319 "create-txn-with-props"));
1321 /* Create our activity URL now on the server. */
1322 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1323 handler->handler_pool = ctx->pool;
1324 handler->method = "POST";
1325 handler->body_type = SVN_SKEL_MIME_TYPE;
1326 handler->body_delegate = create_txn_post_body;
1327 handler->body_delegate_baton =
1328 post_with_revprops ? ctx->revprop_table : NULL;
1329 handler->header_delegate = setup_post_headers;
1330 handler->header_delegate_baton = NULL;
1331 handler->path = ctx->session->me_resource;
1332 handler->conn = ctx->session->conns[0];
1333 handler->session = ctx->session;
1335 prc = apr_pcalloc(ctx->pool, sizeof(*prc));
1336 prc->handler = handler;
1337 prc->commit_ctx = ctx;
1339 handler->response_handler = post_response_handler;
1340 handler->response_baton = prc;
1342 SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1344 if (handler->sline.code != 201)
1346 apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1348 switch (handler->sline.code)
1351 status = SVN_ERR_RA_DAV_FORBIDDEN;
1354 status = SVN_ERR_FS_NOT_FOUND;
1358 return svn_error_createf(status, NULL,
1359 _("%s of '%s': %d %s (%s://%s)"),
1360 handler->method, handler->path,
1361 handler->sline.code, handler->sline.reason,
1362 ctx->session->session_url.scheme,
1363 ctx->session->session_url.hostinfo);
1365 if (! (ctx->txn_root_url && ctx->txn_url))
1367 return svn_error_createf(
1368 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1369 _("POST request did not return transaction information"));
1372 /* Fixup the txn_root_url to point to the anchor of the commit. */
1373 SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
1374 ctx->session->session_url.path,
1375 ctx->session, NULL, dir_pool));
1376 ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
1377 rel_path, ctx->pool);
1379 /* Build our directory baton. */
1380 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1381 dir->pool = dir_pool;
1383 dir->base_revision = base_revision;
1386 dir->changed_props = apr_hash_make(dir->pool);
1387 dir->removed_props = apr_hash_make(dir->pool);
1388 dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
1390 /* If we included our revprops in the POST, we need not
1392 proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
1396 const char *activity_str = ctx->session->activity_collection_url;
1399 SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
1400 ctx->session->conns[0],
1404 /* Cache the result. */
1407 ctx->session->activity_collection_url =
1408 apr_pstrdup(ctx->session->pool, activity_str);
1412 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1413 _("The OPTIONS response did not include the "
1414 "requested activity-collection-set value"));
1418 svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
1421 /* Create our activity URL now on the server. */
1422 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1423 handler->handler_pool = ctx->pool;
1424 handler->method = "MKACTIVITY";
1425 handler->path = ctx->activity_url;
1426 handler->conn = ctx->session->conns[0];
1427 handler->session = ctx->session;
1429 handler->response_handler = svn_ra_serf__expect_empty_body;
1430 handler->response_baton = handler;
1432 SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1434 if (handler->sline.code != 201)
1436 apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1438 switch (handler->sline.code)
1441 status = SVN_ERR_RA_DAV_FORBIDDEN;
1444 status = SVN_ERR_FS_NOT_FOUND;
1448 return svn_error_createf(status, NULL,
1449 _("%s of '%s': %d %s (%s://%s)"),
1450 handler->method, handler->path,
1451 handler->sline.code, handler->sline.reason,
1452 ctx->session->session_url.scheme,
1453 ctx->session->session_url.hostinfo);
1456 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1457 SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
1458 ctx->conn, ctx->pool));
1461 /* Build our directory baton. */
1462 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1463 dir->pool = dir_pool;
1465 dir->base_revision = base_revision;
1468 dir->changed_props = apr_hash_make(dir->pool);
1469 dir->removed_props = apr_hash_make(dir->pool);
1471 SVN_ERR(get_version_url(&dir->url, dir->commit->session,
1473 dir->base_revision, ctx->checked_in_url,
1474 dir->pool, dir->pool /* scratch_pool */));
1475 ctx->checked_in_url = dir->url;
1477 /* Checkout our root dir */
1478 SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
1480 proppatch_target = ctx->baseline_url;
1483 /* Unless this is NULL -- which means we don't need to PROPPATCH the
1484 transaction with our revprops -- then, you know, PROPPATCH the
1485 transaction with our revprops. */
1486 if (proppatch_target)
1488 proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
1489 proppatch_ctx->pool = dir_pool;
1490 proppatch_ctx->commit = ctx;
1491 proppatch_ctx->path = proppatch_target;
1492 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
1493 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
1494 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1496 for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
1497 hi = apr_hash_next(hi))
1499 const char *name = svn__apr_hash_index_key(hi);
1500 svn_string_t *value = svn__apr_hash_index_val(hi);
1503 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1505 ns = SVN_DAV_PROP_NS_SVN;
1506 name += sizeof(SVN_PROP_PREFIX) - 1;
1510 ns = SVN_DAV_PROP_NS_CUSTOM;
1513 svn_ra_serf__set_prop(proppatch_ctx->changed_props,
1514 proppatch_ctx->path,
1515 ns, name, value, proppatch_ctx->pool);
1518 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
1523 return SVN_NO_ERROR;
1526 static svn_error_t *
1527 delete_entry(const char *path,
1528 svn_revnum_t revision,
1532 dir_context_t *dir = parent_baton;
1533 delete_context_t *delete_ctx;
1534 svn_ra_serf__handler_t *handler;
1535 const char *delete_target;
1538 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1540 delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
1545 /* Ensure our directory has been checked out */
1546 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1547 delete_target = svn_path_url_add_component2(dir->working_url,
1548 svn_relpath_basename(path,
1553 /* DELETE our entry */
1554 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1555 delete_ctx->path = apr_pstrdup(pool, path);
1556 delete_ctx->revision = revision;
1557 delete_ctx->lock_token_hash = dir->commit->lock_tokens;
1558 delete_ctx->keep_locks = dir->commit->keep_locks;
1560 handler = apr_pcalloc(pool, sizeof(*handler));
1561 handler->handler_pool = pool;
1562 handler->session = dir->commit->session;
1563 handler->conn = dir->commit->conn;
1565 handler->response_handler = svn_ra_serf__expect_empty_body;
1566 handler->response_baton = handler;
1568 handler->header_delegate = setup_delete_headers;
1569 handler->header_delegate_baton = delete_ctx;
1571 handler->method = "DELETE";
1572 handler->path = delete_target;
1574 err = svn_ra_serf__context_run_one(handler, pool);
1577 (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
1578 err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
1579 err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
1580 err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
1582 svn_error_clear(err);
1584 /* An error has been registered on the connection. Reset the thing
1585 so that we can use it again. */
1586 serf_connection_reset(handler->conn->conn);
1588 handler->body_delegate = create_delete_body;
1589 handler->body_delegate_baton = delete_ctx;
1590 handler->body_type = "text/xml";
1592 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1599 /* 204 No Content: item successfully deleted */
1600 if (handler->sline.code != 204)
1602 return svn_error_trace(return_response_err(handler));
1605 svn_hash_sets(dir->commit->deleted_entries,
1606 apr_pstrdup(dir->commit->pool, path), (void *)1);
1608 return SVN_NO_ERROR;
1611 static svn_error_t *
1612 add_directory(const char *path,
1614 const char *copyfrom_path,
1615 svn_revnum_t copyfrom_revision,
1616 apr_pool_t *dir_pool,
1619 dir_context_t *parent = parent_baton;
1621 svn_ra_serf__handler_t *handler;
1622 apr_status_t status;
1623 const char *mkcol_target;
1625 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1627 dir->pool = dir_pool;
1628 dir->parent_dir = parent;
1629 dir->commit = parent->commit;
1631 dir->base_revision = SVN_INVALID_REVNUM;
1632 dir->copy_revision = copyfrom_revision;
1633 dir->copy_path = copyfrom_path;
1634 dir->relpath = apr_pstrdup(dir->pool, path);
1635 dir->name = svn_relpath_basename(dir->relpath, NULL);
1636 dir->changed_props = apr_hash_make(dir->pool);
1637 dir->removed_props = apr_hash_make(dir->pool);
1639 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1641 dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1643 mkcol_target = dir->url;
1647 /* Ensure our parent is checked out. */
1648 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1650 dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
1651 dir->name, dir->pool);
1652 mkcol_target = svn_path_url_add_component2(
1653 parent->working_url,
1654 dir->name, dir->pool);
1657 handler = apr_pcalloc(dir->pool, sizeof(*handler));
1658 handler->handler_pool = dir->pool;
1659 handler->conn = dir->commit->conn;
1660 handler->session = dir->commit->session;
1662 handler->response_handler = svn_ra_serf__expect_empty_body;
1663 handler->response_baton = handler;
1664 if (!dir->copy_path)
1666 handler->method = "MKCOL";
1667 handler->path = mkcol_target;
1672 const char *req_url;
1674 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1677 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1678 _("Unable to parse URL '%s'"),
1682 /* ### conn==NULL for session->conns[0]. same as commit->conn. */
1683 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1684 dir->commit->session,
1686 uri.path, dir->copy_revision,
1687 dir_pool, dir_pool));
1689 handler->method = "COPY";
1690 handler->path = req_url;
1692 handler->header_delegate = setup_copy_dir_headers;
1693 handler->header_delegate_baton = dir;
1696 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1698 switch (handler->sline.code)
1700 case 201: /* Created: item was successfully copied */
1701 case 204: /* No Content: item successfully replaced an existing target */
1705 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1706 _("Access to '%s' forbidden"),
1709 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1710 _("Adding directory failed: %s on %s "
1712 handler->method, handler->path,
1713 handler->sline.code, handler->sline.reason);
1718 return SVN_NO_ERROR;
1721 static svn_error_t *
1722 open_directory(const char *path,
1724 svn_revnum_t base_revision,
1725 apr_pool_t *dir_pool,
1728 dir_context_t *parent = parent_baton;
1731 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1733 dir->pool = dir_pool;
1735 dir->parent_dir = parent;
1736 dir->commit = parent->commit;
1739 dir->base_revision = base_revision;
1740 dir->relpath = apr_pstrdup(dir->pool, path);
1741 dir->name = svn_relpath_basename(dir->relpath, NULL);
1742 dir->changed_props = apr_hash_make(dir->pool);
1743 dir->removed_props = apr_hash_make(dir->pool);
1745 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1747 dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1752 SVN_ERR(get_version_url(&dir->url,
1753 dir->commit->session,
1754 dir->relpath, dir->base_revision,
1755 dir->commit->checked_in_url,
1756 dir->pool, dir->pool /* scratch_pool */));
1760 return SVN_NO_ERROR;
1763 static svn_error_t *
1764 change_dir_prop(void *dir_baton,
1766 const svn_string_t *value,
1769 dir_context_t *dir = dir_baton;
1771 const char *proppatch_target;
1774 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1776 proppatch_target = dir->url;
1780 /* Ensure we have a checked out dir. */
1781 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1783 proppatch_target = dir->working_url;
1786 name = apr_pstrdup(dir->pool, name);
1787 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1789 ns = SVN_DAV_PROP_NS_SVN;
1790 name += sizeof(SVN_PROP_PREFIX) - 1;
1794 ns = SVN_DAV_PROP_NS_CUSTOM;
1799 value = svn_string_dup(value, dir->pool);
1800 svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
1801 ns, name, value, dir->pool);
1805 value = svn_string_create_empty(dir->pool);
1806 svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
1807 ns, name, value, dir->pool);
1810 return SVN_NO_ERROR;
1813 static svn_error_t *
1814 close_directory(void *dir_baton,
1817 dir_context_t *dir = dir_baton;
1819 /* Huh? We're going to be called before the texts are sent. Ugh.
1820 * Therefore, just wave politely at our caller.
1823 /* PROPPATCH our prop change and pass it along. */
1824 if (apr_hash_count(dir->changed_props) ||
1825 apr_hash_count(dir->removed_props))
1827 proppatch_context_t *proppatch_ctx;
1829 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1830 proppatch_ctx->pool = pool;
1831 proppatch_ctx->commit = dir->commit;
1832 proppatch_ctx->relpath = dir->relpath;
1833 proppatch_ctx->changed_props = dir->changed_props;
1834 proppatch_ctx->removed_props = dir->removed_props;
1835 proppatch_ctx->base_revision = dir->base_revision;
1837 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1839 proppatch_ctx->path = dir->url;
1843 proppatch_ctx->path = dir->working_url;
1846 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
1849 return SVN_NO_ERROR;
1852 static svn_error_t *
1853 add_file(const char *path,
1855 const char *copy_path,
1856 svn_revnum_t copy_revision,
1857 apr_pool_t *file_pool,
1860 dir_context_t *dir = parent_baton;
1861 file_context_t *new_file;
1862 const char *deleted_parent = path;
1864 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1865 new_file->pool = file_pool;
1869 new_file->parent_dir = dir;
1870 new_file->commit = dir->commit;
1871 new_file->relpath = apr_pstrdup(new_file->pool, path);
1872 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1873 new_file->added = TRUE;
1874 new_file->base_revision = SVN_INVALID_REVNUM;
1875 new_file->copy_path = copy_path;
1876 new_file->copy_revision = copy_revision;
1877 new_file->changed_props = apr_hash_make(new_file->pool);
1878 new_file->removed_props = apr_hash_make(new_file->pool);
1880 /* Ensure that the file doesn't exist by doing a HEAD on the
1881 resource. If we're using HTTP v2, we'll just look into the
1882 transaction root tree for this thing. */
1883 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1885 new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
1886 path, new_file->pool);
1890 /* Ensure our parent directory has been checked out */
1891 SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
1894 svn_path_url_add_component2(dir->working_url,
1895 new_file->name, new_file->pool);
1898 while (deleted_parent && deleted_parent[0] != '\0')
1900 if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
1904 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1907 if (! ((dir->added && !dir->copy_path) ||
1908 (deleted_parent && deleted_parent[0] != '\0')))
1910 svn_ra_serf__handler_t *handler;
1912 handler = apr_pcalloc(new_file->pool, sizeof(*handler));
1913 handler->handler_pool = new_file->pool;
1914 handler->session = new_file->commit->session;
1915 handler->conn = new_file->commit->conn;
1916 handler->method = "HEAD";
1917 handler->path = svn_path_url_add_component2(
1918 dir->commit->session->session_url.path,
1919 path, new_file->pool);
1920 handler->response_handler = svn_ra_serf__expect_empty_body;
1921 handler->response_baton = handler;
1923 SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
1925 if (handler->sline.code != 404)
1927 return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
1928 _("File '%s' already exists"), path);
1932 *file_baton = new_file;
1934 return SVN_NO_ERROR;
1937 static svn_error_t *
1938 open_file(const char *path,
1940 svn_revnum_t base_revision,
1941 apr_pool_t *file_pool,
1944 dir_context_t *parent = parent_baton;
1945 file_context_t *new_file;
1947 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1948 new_file->pool = file_pool;
1950 parent->ref_count++;
1952 new_file->parent_dir = parent;
1953 new_file->commit = parent->commit;
1954 new_file->relpath = apr_pstrdup(new_file->pool, path);
1955 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1956 new_file->added = FALSE;
1957 new_file->base_revision = base_revision;
1958 new_file->changed_props = apr_hash_make(new_file->pool);
1959 new_file->removed_props = apr_hash_make(new_file->pool);
1961 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
1963 new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1964 path, new_file->pool);
1968 /* CHECKOUT the file into our activity. */
1969 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1971 new_file->url = new_file->working_url;
1974 *file_baton = new_file;
1976 return SVN_NO_ERROR;
1979 static svn_error_t *
1980 apply_textdelta(void *file_baton,
1981 const char *base_checksum,
1983 svn_txdelta_window_handler_t *handler,
1984 void **handler_baton)
1986 file_context_t *ctx = file_baton;
1988 /* Store the stream in a temporary file; we'll give it to serf when we
1991 * TODO: There should be a way we can stream the request body instead of
1992 * writing to a temporary file (ugh). A special svn stream serf bucket
1993 * that returns EAGAIN until we receive the done call? But, when
1994 * would we run through the serf context? Grr.
1996 * ctx->pool is the same for all files in the commit that send a
1997 * textdelta so this file is explicitly closed in close_file to
1998 * avoid too many simultaneously open files.
2001 SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
2002 svn_io_file_del_on_pool_cleanup,
2005 ctx->stream = svn_stream_create(ctx, pool);
2006 svn_stream_set_write(ctx->stream, svndiff_stream_write);
2008 svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
2009 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
2012 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
2014 return SVN_NO_ERROR;
2017 static svn_error_t *
2018 change_file_prop(void *file_baton,
2020 const svn_string_t *value,
2023 file_context_t *file = file_baton;
2026 name = apr_pstrdup(file->pool, name);
2028 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2030 ns = SVN_DAV_PROP_NS_SVN;
2031 name += sizeof(SVN_PROP_PREFIX) - 1;
2035 ns = SVN_DAV_PROP_NS_CUSTOM;
2040 value = svn_string_dup(value, file->pool);
2041 svn_ra_serf__set_prop(file->changed_props, file->url,
2042 ns, name, value, file->pool);
2046 value = svn_string_create_empty(file->pool);
2048 svn_ra_serf__set_prop(file->removed_props, file->url,
2049 ns, name, value, file->pool);
2052 return SVN_NO_ERROR;
2055 static svn_error_t *
2056 close_file(void *file_baton,
2057 const char *text_checksum,
2058 apr_pool_t *scratch_pool)
2060 file_context_t *ctx = file_baton;
2061 svn_boolean_t put_empty_file = FALSE;
2062 apr_status_t status;
2064 ctx->result_checksum = text_checksum;
2068 svn_ra_serf__handler_t *handler;
2070 const char *req_url;
2072 status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
2075 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2076 _("Unable to parse URL '%s'"),
2080 /* ### conn==NULL for session->conns[0]. same as commit->conn. */
2081 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
2082 ctx->commit->session,
2084 uri.path, ctx->copy_revision,
2085 scratch_pool, scratch_pool));
2087 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2088 handler->handler_pool = scratch_pool;
2089 handler->method = "COPY";
2090 handler->path = req_url;
2091 handler->conn = ctx->commit->conn;
2092 handler->session = ctx->commit->session;
2094 handler->response_handler = svn_ra_serf__expect_empty_body;
2095 handler->response_baton = handler;
2097 handler->header_delegate = setup_copy_file_headers;
2098 handler->header_delegate_baton = ctx;
2100 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2102 if (handler->sline.code != 201 && handler->sline.code != 204)
2104 return svn_error_trace(return_response_err(handler));
2108 /* If we got no stream of changes, but this is an added-without-history
2109 * file, make a note that we'll be PUTting a zero-byte file to the server.
2111 if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
2112 put_empty_file = TRUE;
2114 /* If we had a stream of changes, push them to the server... */
2115 if (ctx->stream || put_empty_file)
2117 svn_ra_serf__handler_t *handler;
2119 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2120 handler->handler_pool = scratch_pool;
2121 handler->method = "PUT";
2122 handler->path = ctx->url;
2123 handler->conn = ctx->commit->conn;
2124 handler->session = ctx->commit->session;
2126 handler->response_handler = svn_ra_serf__expect_empty_body;
2127 handler->response_baton = handler;
2131 handler->body_delegate = create_empty_put_body;
2132 handler->body_delegate_baton = ctx;
2133 handler->body_type = "text/plain";
2137 handler->body_delegate = create_put_body;
2138 handler->body_delegate_baton = ctx;
2139 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2142 handler->header_delegate = setup_put_headers;
2143 handler->header_delegate_baton = ctx;
2145 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2147 if (handler->sline.code != 204 && handler->sline.code != 201)
2149 return svn_error_trace(return_response_err(handler));
2154 SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2156 /* If we had any prop changes, push them via PROPPATCH. */
2157 if (apr_hash_count(ctx->changed_props) ||
2158 apr_hash_count(ctx->removed_props))
2160 proppatch_context_t *proppatch;
2162 proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
2163 proppatch->pool = ctx->pool;
2164 proppatch->relpath = ctx->relpath;
2165 proppatch->path = ctx->url;
2166 proppatch->commit = ctx->commit;
2167 proppatch->changed_props = ctx->changed_props;
2168 proppatch->removed_props = ctx->removed_props;
2169 proppatch->base_revision = ctx->base_revision;
2171 SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
2174 return SVN_NO_ERROR;
2177 static svn_error_t *
2178 close_edit(void *edit_baton,
2181 commit_context_t *ctx = edit_baton;
2182 const char *merge_target =
2183 ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2184 const svn_commit_info_t *commit_info;
2187 /* MERGE our activity */
2188 SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
2190 ctx->session->conns[0],
2196 if (response_code != 200)
2198 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2199 _("MERGE request failed: returned %d "
2204 /* Inform the WC that we did a commit. */
2206 SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
2208 /* If we're using activities, DELETE our completed activity. */
2209 if (ctx->activity_url)
2211 svn_ra_serf__handler_t *handler;
2213 handler = apr_pcalloc(pool, sizeof(*handler));
2214 handler->handler_pool = pool;
2215 handler->method = "DELETE";
2216 handler->path = ctx->activity_url;
2217 handler->conn = ctx->conn;
2218 handler->session = ctx->session;
2220 handler->response_handler = svn_ra_serf__expect_empty_body;
2221 handler->response_baton = handler;
2223 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2225 SVN_ERR_ASSERT(handler->sline.code == 204);
2228 return SVN_NO_ERROR;
2231 static svn_error_t *
2232 abort_edit(void *edit_baton,
2235 commit_context_t *ctx = edit_baton;
2236 svn_ra_serf__handler_t *handler;
2238 /* If an activity or transaction wasn't even created, don't bother
2239 trying to delete it. */
2240 if (! (ctx->activity_url || ctx->txn_url))
2241 return SVN_NO_ERROR;
2243 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2244 had a problem. We need to reset it, in order to use it again. */
2245 serf_connection_reset(ctx->session->conns[0]->conn);
2247 /* DELETE our aborted activity */
2248 handler = apr_pcalloc(pool, sizeof(*handler));
2249 handler->handler_pool = pool;
2250 handler->method = "DELETE";
2251 handler->conn = ctx->session->conns[0];
2252 handler->session = ctx->session;
2254 handler->response_handler = svn_ra_serf__expect_empty_body;
2255 handler->response_baton = handler;
2257 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2258 handler->path = ctx->txn_url;
2260 handler->path = ctx->activity_url;
2262 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2265 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2266 404 if the activity wasn't found. */
2267 if (handler->sline.code != 204
2268 && handler->sline.code != 403
2269 && handler->sline.code != 404
2272 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2273 _("DELETE returned unexpected status: %d"),
2274 handler->sline.code);
2277 return SVN_NO_ERROR;
2281 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2282 const svn_delta_editor_t **ret_editor,
2284 apr_hash_t *revprop_table,
2285 svn_commit_callback2_t callback,
2286 void *callback_baton,
2287 apr_hash_t *lock_tokens,
2288 svn_boolean_t keep_locks,
2291 svn_ra_serf__session_t *session = ra_session->priv;
2292 svn_delta_editor_t *editor;
2293 commit_context_t *ctx;
2294 const char *repos_root;
2295 const char *base_relpath;
2296 svn_boolean_t supports_ephemeral_props;
2298 ctx = apr_pcalloc(pool, sizeof(*ctx));
2302 ctx->session = session;
2303 ctx->conn = session->conns[0];
2305 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2307 /* If the server supports ephemeral properties, add some carrying
2308 interesting version information. */
2309 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2310 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2312 if (supports_ephemeral_props)
2314 svn_hash_sets(ctx->revprop_table,
2315 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2316 svn_string_create(SVN_VER_NUMBER, pool));
2317 svn_hash_sets(ctx->revprop_table,
2318 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2319 svn_string_create(session->useragent, pool));
2322 ctx->callback = callback;
2323 ctx->callback_baton = callback_baton;
2325 ctx->lock_tokens = lock_tokens;
2326 ctx->keep_locks = keep_locks;
2328 ctx->deleted_entries = apr_hash_make(ctx->pool);
2330 editor = svn_delta_default_editor(pool);
2331 editor->open_root = open_root;
2332 editor->delete_entry = delete_entry;
2333 editor->add_directory = add_directory;
2334 editor->open_directory = open_directory;
2335 editor->change_dir_prop = change_dir_prop;
2336 editor->close_directory = close_directory;
2337 editor->add_file = add_file;
2338 editor->open_file = open_file;
2339 editor->apply_textdelta = apply_textdelta;
2340 editor->change_file_prop = change_file_prop;
2341 editor->close_file = close_file;
2342 editor->close_edit = close_edit;
2343 editor->abort_edit = abort_edit;
2345 *ret_editor = editor;
2348 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2349 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2352 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2353 *edit_baton, repos_root, base_relpath,
2354 session->shim_callbacks, pool, pool));
2356 return SVN_NO_ERROR;
2360 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2363 const svn_string_t *const *old_value_p,
2364 const svn_string_t *value,
2367 svn_ra_serf__session_t *session = ra_session->priv;
2368 proppatch_context_t *proppatch_ctx;
2369 commit_context_t *commit;
2370 const char *proppatch_target;
2376 svn_boolean_t capable;
2377 SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
2378 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2381 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2382 SVN_ERR_ASSERT(capable);
2385 commit = apr_pcalloc(pool, sizeof(*commit));
2387 commit->pool = pool;
2389 commit->session = session;
2390 commit->conn = session->conns[0];
2392 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2394 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2398 const char *vcc_url;
2400 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
2401 commit->conn, pool));
2403 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2404 commit->conn, vcc_url, rev,
2409 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2411 ns = SVN_DAV_PROP_NS_SVN;
2412 name += sizeof(SVN_PROP_PREFIX) - 1;
2416 ns = SVN_DAV_PROP_NS_CUSTOM;
2419 /* PROPPATCH our log message and pass it along. */
2420 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2421 proppatch_ctx->pool = pool;
2422 proppatch_ctx->commit = commit;
2423 proppatch_ctx->path = proppatch_target;
2424 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
2425 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
2428 proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
2429 proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
2431 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2433 if (old_value_p && *old_value_p)
2435 svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
2436 proppatch_ctx->path,
2437 ns, name, *old_value_p, proppatch_ctx->pool);
2439 else if (old_value_p)
2441 svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
2443 svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
2444 proppatch_ctx->path,
2445 ns, name, dummy_value, proppatch_ctx->pool);
2450 svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
2451 ns, name, value, proppatch_ctx->pool);
2455 value = svn_string_create_empty(proppatch_ctx->pool);
2457 svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
2458 ns, name, value, proppatch_ctx->pool);
2461 err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
2465 (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
2466 _("DAV request failed; it's possible that the repository's "
2467 "pre-revprop-change hook either failed or is non-existent"));
2469 return SVN_NO_ERROR;