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 /* Calculate the working_url by skipping the shared ancestor bewteen
401 * the parent->relpath and dir->relpath. This is safe since an
402 * add is guaranteed to have a parent that is checked out. */
403 dir_context_t *parent = p_dir->parent_dir;
404 const char *relpath = svn_relpath_skip_ancestor(parent->relpath,
407 /* Implicitly checkout this dir now. */
408 SVN_ERR_ASSERT(parent->working_url);
409 dir->working_url = svn_path_url_add_component2(
414 p_dir = p_dir->parent_dir;
417 /* We could be called twice for the root: once to checkout the baseline;
418 * once to checkout the directory itself if we need to do so.
419 * Note: CHECKOUT_URL should live longer than HANDLER.
421 if (!dir->parent_dir && !dir->commit->baseline_url)
423 checkout_url = dir->commit->vcc_url;
424 working = &dir->commit->baseline_url;
428 checkout_url = dir->url;
429 working = &dir->working_url;
432 /* Checkout our directory into the activity URL now. */
433 err = retry_checkout_node(working, dir->commit, checkout_url,
434 dir->pool, scratch_pool);
437 if (err->apr_err == SVN_ERR_FS_CONFLICT)
438 SVN_ERR_W(err, apr_psprintf(scratch_pool,
439 _("Directory '%s' is out of date; try updating"),
440 svn_dirent_local_style(dir->relpath, scratch_pool)));
448 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
449 * RELPATH (relative to the root of SESSION).
451 * Try to find this version url in three ways:
452 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
453 * version url from the working copy properties.
454 * Second, if the version url of the parent directory PARENT_VSN_URL is
455 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
457 * Else, fetch the version url for the root of SESSION using CONN and
458 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
461 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
462 * temporary allocation.
465 get_version_url(const char **checked_in_url,
466 svn_ra_serf__session_t *session,
468 svn_revnum_t base_revision,
469 const char *parent_vsn_url,
470 apr_pool_t *result_pool,
471 apr_pool_t *scratch_pool)
473 const char *root_checkout;
475 if (session->wc_callbacks->get_wc_prop)
477 const svn_string_t *current_version;
479 SVN_ERR(session->wc_callbacks->get_wc_prop(
480 session->wc_callback_baton,
482 SVN_RA_SERF__WC_CHECKED_IN_URL,
483 ¤t_version, scratch_pool));
488 svn_urlpath__canonicalize(current_version->data, result_pool);
495 root_checkout = parent_vsn_url;
499 const char *propfind_url;
500 svn_ra_serf__connection_t *conn = session->conns[0];
502 if (SVN_IS_VALID_REVNUM(base_revision))
504 /* mod_dav_svn can't handle the "Label:" header that
505 svn_ra_serf__deliver_props() is going to try to use for
506 this lookup, so we'll do things the hard(er) way, by
507 looking up the version URL from a resource in the
508 baseline collection. */
509 /* ### conn==NULL for session->conns[0]. same as CONN. */
510 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
511 NULL /* latest_revnum */,
512 session, NULL /* conn */,
513 NULL /* url */, base_revision,
514 scratch_pool, scratch_pool));
518 propfind_url = session->session_url.path;
521 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
522 conn, propfind_url, base_revision,
524 scratch_pool, scratch_pool));
526 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
527 _("Path '%s' not present"),
528 session->session_url.path);
530 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
533 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
540 checkout_file(file_context_t *file,
541 apr_pool_t *scratch_pool)
544 dir_context_t *parent_dir = file->parent_dir;
545 const char *checkout_url;
547 /* Is one of our parent dirs newly added? If so, we're already
548 * implicitly checked out.
552 if (parent_dir->added)
554 /* Implicitly checkout this file now. */
555 file->working_url = svn_path_url_add_component2(
556 parent_dir->working_url,
557 svn_relpath_skip_ancestor(
558 parent_dir->relpath, file->relpath),
562 parent_dir = parent_dir->parent_dir;
565 SVN_ERR(get_version_url(&checkout_url,
566 file->commit->session,
567 file->relpath, file->base_revision,
568 NULL, scratch_pool, scratch_pool));
570 /* Checkout our file into the activity URL now. */
571 err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
572 file->pool, scratch_pool);
575 if (err->apr_err == SVN_ERR_FS_CONFLICT)
576 SVN_ERR_W(err, apr_psprintf(scratch_pool,
577 _("File '%s' is out of date; try updating"),
578 svn_dirent_local_style(file->relpath, scratch_pool)));
585 /* Helper function for proppatch_walker() below. */
587 get_encoding_and_cdata(const char **encoding_p,
588 const svn_string_t **encoded_value_p,
589 serf_bucket_alloc_t *alloc,
590 const svn_string_t *value,
591 apr_pool_t *result_pool,
592 apr_pool_t *scratch_pool)
597 *encoded_value_p = NULL;
601 /* If a property is XML-safe, XML-encode it. Else, base64-encode
603 if (svn_xml_is_xml_safe(value->data, value->len))
605 svn_stringbuf_t *xml_esc = NULL;
606 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
608 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
612 *encoding_p = "base64";
613 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
619 typedef struct walker_baton_t {
620 serf_bucket_t *body_bkt;
621 apr_pool_t *body_pool;
623 apr_hash_t *previous_changed_props;
624 apr_hash_t *previous_removed_props;
628 /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
629 rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */
632 filter_props_with_old_value,
633 filter_props_without_old_value
636 /* Is the property being deleted? */
637 svn_boolean_t deleting;
640 /* If we have (recorded in WB) the old value of the property named NS:NAME,
641 * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
642 * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */
644 derive_old_val(svn_boolean_t *have_old_val,
645 const svn_string_t **old_val_p,
650 *have_old_val = FALSE;
652 if (wb->previous_changed_props)
654 const svn_string_t *val;
655 val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
659 *have_old_val = TRUE;
664 if (wb->previous_removed_props)
666 const svn_string_t *val;
667 val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
671 *have_old_val = TRUE;
680 proppatch_walker(void *baton,
683 const svn_string_t *val,
684 apr_pool_t *scratch_pool)
686 walker_baton_t *wb = baton;
687 serf_bucket_t *body_bkt = wb->body_bkt;
688 serf_bucket_t *cdata_bkt;
689 serf_bucket_alloc_t *alloc;
690 const char *encoding;
691 svn_boolean_t have_old_val;
692 const svn_string_t *old_val;
693 const svn_string_t *encoded_value;
694 const char *prop_name;
696 SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
698 /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
700 if (wb->filter != filter_all_props)
702 if (wb->filter == filter_props_with_old_value && ! have_old_val)
704 if (wb->filter == filter_props_without_old_value && have_old_val)
710 alloc = body_bkt->allocator;
712 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
713 wb->body_pool, scratch_pool));
716 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
725 /* Use the namespace prefix instead of adding the xmlns attribute to support
726 property names containing ':' */
727 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
728 prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
729 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
730 prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
733 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
734 "V:encoding", encoding,
737 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
738 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
743 const char *encoding2;
744 const svn_string_t *encoded_value2;
745 serf_bucket_t *cdata_bkt2;
747 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
749 wb->body_pool, scratch_pool));
753 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
763 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
764 "V:" SVN_DAV__OLD_VALUE,
765 "V:encoding", encoding2,
768 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
769 "V:" SVN_DAV__OLD_VALUE,
770 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
774 serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
776 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
777 "V:" SVN_DAV__OLD_VALUE);
780 serf_bucket_aggregate_append(body_bkt, cdata_bkt);
781 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
786 /* Possible add the lock-token "If:" precondition header to HEADERS if
787 an examination of COMMIT_CTX and RELPATH indicates that this is the
790 Generally speaking, if the client provided a lock token for
791 RELPATH, it's the right thing to do. There is a notable instance
792 where this is not the case, however. If the file at RELPATH was
793 explicitly deleted in this commit already, then mod_dav removed its
794 lock token when it fielded the DELETE request, so we don't want to
795 set the lock precondition again. (See
796 http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
799 maybe_set_lock_token_header(serf_bucket_t *headers,
800 commit_context_t *commit_ctx,
806 if (! (relpath && commit_ctx->lock_tokens))
809 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
811 token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
814 const char *token_header;
815 const char *token_uri;
816 apr_uri_t uri = commit_ctx->session->session_url;
818 /* Supplying the optional URI affects apache response when
819 the lock is broken, see issue 4369. When present any URI
820 must be absolute (RFC 2518 9.4). */
821 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
823 token_uri = apr_uri_unparse(pool, &uri, 0);
825 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
827 serf_bucket_headers_set(headers, "If", token_header);
835 setup_proppatch_headers(serf_bucket_t *headers,
839 proppatch_context_t *proppatch = baton;
841 if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
843 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
844 apr_psprintf(pool, "%ld",
845 proppatch->base_revision));
848 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
849 proppatch->relpath, pool));
855 struct proppatch_body_baton_t {
856 proppatch_context_t *proppatch;
858 /* Content in the body should be allocated here, to live long enough. */
859 apr_pool_t *body_pool;
862 /* Implements svn_ra_serf__request_body_delegate_t */
864 create_proppatch_body(serf_bucket_t **bkt,
866 serf_bucket_alloc_t *alloc,
867 apr_pool_t *scratch_pool)
869 struct proppatch_body_baton_t *pbb = baton;
870 proppatch_context_t *ctx = pbb->proppatch;
871 serf_bucket_t *body_bkt;
872 walker_baton_t wb = { 0 };
874 body_bkt = serf_bucket_aggregate_create(alloc);
876 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
877 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
879 "xmlns:V", SVN_DAV_PROP_NS_DAV,
880 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
881 "xmlns:S", SVN_DAV_PROP_NS_SVN,
884 wb.body_bkt = body_bkt;
885 wb.body_pool = pbb->body_pool;
886 wb.previous_changed_props = ctx->previous_changed_props;
887 wb.previous_removed_props = ctx->previous_removed_props;
890 if (apr_hash_count(ctx->changed_props) > 0)
892 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
893 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
895 wb.filter = filter_all_props;
897 SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
899 proppatch_walker, &wb,
902 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
903 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
906 if (apr_hash_count(ctx->removed_props) > 0)
908 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
909 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
911 wb.filter = filter_props_with_old_value;
913 SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
915 proppatch_walker, &wb,
918 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
919 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
922 if (apr_hash_count(ctx->removed_props) > 0)
924 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
925 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
927 wb.filter = filter_props_without_old_value;
929 SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
931 proppatch_walker, &wb,
934 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
935 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
938 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
945 proppatch_resource(proppatch_context_t *proppatch,
946 commit_context_t *commit,
949 svn_ra_serf__handler_t *handler;
950 struct proppatch_body_baton_t pbb;
952 handler = apr_pcalloc(pool, sizeof(*handler));
953 handler->handler_pool = pool;
954 handler->method = "PROPPATCH";
955 handler->path = proppatch->path;
956 handler->conn = commit->conn;
957 handler->session = commit->session;
959 handler->header_delegate = setup_proppatch_headers;
960 handler->header_delegate_baton = proppatch;
962 pbb.proppatch = proppatch;
963 pbb.body_pool = pool;
964 handler->body_delegate = create_proppatch_body;
965 handler->body_delegate_baton = &pbb;
967 handler->response_handler = svn_ra_serf__handle_multistatus_only;
968 handler->response_baton = handler;
970 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
972 if (handler->sline.code != 207
973 || (handler->server_error != NULL
974 && handler->server_error->error != NULL))
976 return svn_error_create(
977 SVN_ERR_RA_DAV_PROPPATCH_FAILED,
978 return_response_err(handler),
979 _("At least one property change failed; repository"
986 /* Implements svn_ra_serf__request_body_delegate_t */
988 create_put_body(serf_bucket_t **body_bkt,
990 serf_bucket_alloc_t *alloc,
993 file_context_t *ctx = baton;
996 /* We need to flush the file, make it unbuffered (so that it can be
997 * zero-copied via mmap), and reset the position before attempting to
1000 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
1001 * and zero-copy the PUT body. However, on older APR versions, we can't
1002 * check the buffer status; but serf will fall through and create a file
1003 * bucket for us on the buffered svndiff handle.
1005 apr_file_flush(ctx->svndiff);
1006 #if APR_VERSION_AT_LEAST(1, 3, 0)
1007 apr_file_buffer_set(ctx->svndiff, NULL, 0);
1010 apr_file_seek(ctx->svndiff, APR_SET, &offset);
1012 *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
1013 return SVN_NO_ERROR;
1016 /* Implements svn_ra_serf__request_body_delegate_t */
1017 static svn_error_t *
1018 create_empty_put_body(serf_bucket_t **body_bkt,
1020 serf_bucket_alloc_t *alloc,
1023 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
1024 return SVN_NO_ERROR;
1027 static svn_error_t *
1028 setup_put_headers(serf_bucket_t *headers,
1032 file_context_t *ctx = baton;
1034 if (SVN_IS_VALID_REVNUM(ctx->base_revision))
1036 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1037 apr_psprintf(pool, "%ld", ctx->base_revision));
1040 if (ctx->base_checksum)
1042 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
1043 ctx->base_checksum);
1046 if (ctx->result_checksum)
1048 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
1049 ctx->result_checksum);
1052 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
1053 ctx->relpath, pool));
1058 static svn_error_t *
1059 setup_copy_file_headers(serf_bucket_t *headers,
1063 file_context_t *file = baton;
1065 const char *absolute_uri;
1067 /* The Dest URI must be absolute. Bummer. */
1068 uri = file->commit->session->session_url;
1069 uri.path = (char*)file->url;
1070 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1072 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1074 serf_bucket_headers_setn(headers, "Depth", "0");
1075 serf_bucket_headers_setn(headers, "Overwrite", "T");
1077 return SVN_NO_ERROR;
1080 static svn_error_t *
1081 setup_copy_dir_headers(serf_bucket_t *headers,
1085 dir_context_t *dir = baton;
1087 const char *absolute_uri;
1089 /* The Dest URI must be absolute. Bummer. */
1090 uri = dir->commit->session->session_url;
1092 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1094 uri.path = (char *)dir->url;
1098 uri.path = (char *)svn_path_url_add_component2(
1099 dir->parent_dir->working_url,
1102 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1104 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1106 serf_bucket_headers_setn(headers, "Depth", "infinity");
1107 serf_bucket_headers_setn(headers, "Overwrite", "T");
1109 /* Implicitly checkout this dir now. */
1110 dir->working_url = apr_pstrdup(dir->pool, uri.path);
1112 return SVN_NO_ERROR;
1115 static svn_error_t *
1116 setup_delete_headers(serf_bucket_t *headers,
1120 delete_context_t *ctx = baton;
1122 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1123 apr_ltoa(pool, ctx->revision));
1125 if (ctx->lock_token_hash)
1127 ctx->lock_token = svn_hash_gets(ctx->lock_token_hash, ctx->path);
1129 if (ctx->lock_token)
1131 const char *token_header;
1133 token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
1134 ctx->lock_token, ">)", (char *)NULL);
1136 serf_bucket_headers_set(headers, "If", token_header);
1138 if (ctx->keep_locks)
1139 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1140 SVN_DAV_OPTION_KEEP_LOCKS);
1144 return SVN_NO_ERROR;
1147 /* Implements svn_ra_serf__request_body_delegate_t */
1148 static svn_error_t *
1149 create_delete_body(serf_bucket_t **body_bkt,
1151 serf_bucket_alloc_t *alloc,
1154 delete_context_t *ctx = baton;
1155 serf_bucket_t *body;
1157 body = serf_bucket_aggregate_create(alloc);
1159 svn_ra_serf__add_xml_header_buckets(body, alloc);
1161 svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
1165 return SVN_NO_ERROR;
1168 /* Helper function to write the svndiff stream to temporary file. */
1169 static svn_error_t *
1170 svndiff_stream_write(void *file_baton,
1174 file_context_t *ctx = file_baton;
1175 apr_status_t status;
1177 status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
1179 return svn_error_wrap_apr(status, _("Failed writing updated file"));
1181 return SVN_NO_ERROR;
1186 /* POST against 'me' resource handlers. */
1188 /* Implements svn_ra_serf__request_body_delegate_t */
1189 static svn_error_t *
1190 create_txn_post_body(serf_bucket_t **body_bkt,
1192 serf_bucket_alloc_t *alloc,
1195 apr_hash_t *revprops = baton;
1196 svn_skel_t *request_skel;
1197 svn_stringbuf_t *skel_str;
1199 request_skel = svn_skel__make_empty_list(pool);
1202 svn_skel_t *proplist_skel;
1204 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1205 svn_skel__prepend(proplist_skel, request_skel);
1206 svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1207 skel_str = svn_skel__unparse(request_skel, pool);
1208 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1212 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1215 return SVN_NO_ERROR;
1218 /* Implements svn_ra_serf__request_header_delegate_t */
1219 static svn_error_t *
1220 setup_post_headers(serf_bucket_t *headers,
1224 #ifdef SVN_DAV_SEND_VTXN_NAME
1225 /* Enable this to exercise the VTXN-NAME code based on a client
1226 supplied transaction name. */
1227 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1228 svn_uuid_generate(pool));
1231 return SVN_NO_ERROR;
1235 /* Handler baton for POST request. */
1236 typedef struct post_response_ctx_t
1238 svn_ra_serf__handler_t *handler;
1239 commit_context_t *commit_ctx;
1240 } post_response_ctx_t;
1243 /* This implements serf_bucket_headers_do_callback_fn_t. */
1245 post_headers_iterator_callback(void *baton,
1249 post_response_ctx_t *prc = baton;
1250 commit_context_t *prc_cc = prc->commit_ctx;
1251 svn_ra_serf__session_t *sess = prc_cc->session;
1253 /* If we provided a UUID to the POST request, we should get back
1254 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1255 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
1258 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1260 /* Build out txn and txn-root URLs using the txn name we're
1261 given, and store the whole lot of it in the commit context. */
1263 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1264 prc_cc->txn_root_url =
1265 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1268 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1270 /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1271 given, and store the whole lot of it in the commit context. */
1273 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1274 prc_cc->txn_root_url =
1275 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1282 /* A custom serf_response_handler_t which is mostly a wrapper around
1283 svn_ra_serf__expect_empty_body -- it just notices POST response
1286 Implements svn_ra_serf__response_handler_t */
1287 static svn_error_t *
1288 post_response_handler(serf_request_t *request,
1289 serf_bucket_t *response,
1291 apr_pool_t *scratch_pool)
1293 post_response_ctx_t *prc = baton;
1294 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1296 /* Then see which ones we can discover. */
1297 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1299 /* Execute the 'real' response handler to XML-parse the repsonse body. */
1300 return svn_ra_serf__expect_empty_body(request, response,
1301 prc->handler, scratch_pool);
1306 /* Commit baton callbacks */
1308 static svn_error_t *
1309 open_root(void *edit_baton,
1310 svn_revnum_t base_revision,
1311 apr_pool_t *dir_pool,
1314 commit_context_t *ctx = edit_baton;
1315 svn_ra_serf__handler_t *handler;
1316 proppatch_context_t *proppatch_ctx;
1318 apr_hash_index_t *hi;
1319 const char *proppatch_target = NULL;
1321 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
1323 post_response_ctx_t *prc;
1324 const char *rel_path;
1325 svn_boolean_t post_with_revprops
1326 = (NULL != svn_hash_gets(ctx->session->supported_posts,
1327 "create-txn-with-props"));
1329 /* Create our activity URL now on the server. */
1330 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1331 handler->handler_pool = ctx->pool;
1332 handler->method = "POST";
1333 handler->body_type = SVN_SKEL_MIME_TYPE;
1334 handler->body_delegate = create_txn_post_body;
1335 handler->body_delegate_baton =
1336 post_with_revprops ? ctx->revprop_table : NULL;
1337 handler->header_delegate = setup_post_headers;
1338 handler->header_delegate_baton = NULL;
1339 handler->path = ctx->session->me_resource;
1340 handler->conn = ctx->session->conns[0];
1341 handler->session = ctx->session;
1343 prc = apr_pcalloc(ctx->pool, sizeof(*prc));
1344 prc->handler = handler;
1345 prc->commit_ctx = ctx;
1347 handler->response_handler = post_response_handler;
1348 handler->response_baton = prc;
1350 SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1352 if (handler->sline.code != 201)
1354 apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1356 switch (handler->sline.code)
1359 status = SVN_ERR_RA_DAV_FORBIDDEN;
1362 status = SVN_ERR_FS_NOT_FOUND;
1366 return svn_error_createf(status, NULL,
1367 _("%s of '%s': %d %s (%s://%s)"),
1368 handler->method, handler->path,
1369 handler->sline.code, handler->sline.reason,
1370 ctx->session->session_url.scheme,
1371 ctx->session->session_url.hostinfo);
1373 if (! (ctx->txn_root_url && ctx->txn_url))
1375 return svn_error_createf(
1376 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1377 _("POST request did not return transaction information"));
1380 /* Fixup the txn_root_url to point to the anchor of the commit. */
1381 SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
1382 ctx->session->session_url.path,
1383 ctx->session, NULL, dir_pool));
1384 ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
1385 rel_path, ctx->pool);
1387 /* Build our directory baton. */
1388 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1389 dir->pool = dir_pool;
1391 dir->base_revision = base_revision;
1394 dir->changed_props = apr_hash_make(dir->pool);
1395 dir->removed_props = apr_hash_make(dir->pool);
1396 dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
1398 /* If we included our revprops in the POST, we need not
1400 proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
1404 const char *activity_str = ctx->session->activity_collection_url;
1407 SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
1408 ctx->session->conns[0],
1412 /* Cache the result. */
1415 ctx->session->activity_collection_url =
1416 apr_pstrdup(ctx->session->pool, activity_str);
1420 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1421 _("The OPTIONS response did not include the "
1422 "requested activity-collection-set value"));
1426 svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
1429 /* Create our activity URL now on the server. */
1430 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1431 handler->handler_pool = ctx->pool;
1432 handler->method = "MKACTIVITY";
1433 handler->path = ctx->activity_url;
1434 handler->conn = ctx->session->conns[0];
1435 handler->session = ctx->session;
1437 handler->response_handler = svn_ra_serf__expect_empty_body;
1438 handler->response_baton = handler;
1440 SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1442 if (handler->sline.code != 201)
1444 apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1446 switch (handler->sline.code)
1449 status = SVN_ERR_RA_DAV_FORBIDDEN;
1452 status = SVN_ERR_FS_NOT_FOUND;
1456 return svn_error_createf(status, NULL,
1457 _("%s of '%s': %d %s (%s://%s)"),
1458 handler->method, handler->path,
1459 handler->sline.code, handler->sline.reason,
1460 ctx->session->session_url.scheme,
1461 ctx->session->session_url.hostinfo);
1464 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1465 SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
1466 ctx->conn, ctx->pool));
1469 /* Build our directory baton. */
1470 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1471 dir->pool = dir_pool;
1473 dir->base_revision = base_revision;
1476 dir->changed_props = apr_hash_make(dir->pool);
1477 dir->removed_props = apr_hash_make(dir->pool);
1479 SVN_ERR(get_version_url(&dir->url, dir->commit->session,
1481 dir->base_revision, ctx->checked_in_url,
1482 dir->pool, dir->pool /* scratch_pool */));
1483 ctx->checked_in_url = dir->url;
1485 /* Checkout our root dir */
1486 SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
1488 proppatch_target = ctx->baseline_url;
1491 /* Unless this is NULL -- which means we don't need to PROPPATCH the
1492 transaction with our revprops -- then, you know, PROPPATCH the
1493 transaction with our revprops. */
1494 if (proppatch_target)
1496 proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
1497 proppatch_ctx->pool = dir_pool;
1498 proppatch_ctx->commit = ctx;
1499 proppatch_ctx->path = proppatch_target;
1500 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
1501 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
1502 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1504 for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
1505 hi = apr_hash_next(hi))
1507 const char *name = svn__apr_hash_index_key(hi);
1508 svn_string_t *value = svn__apr_hash_index_val(hi);
1511 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1513 ns = SVN_DAV_PROP_NS_SVN;
1514 name += sizeof(SVN_PROP_PREFIX) - 1;
1518 ns = SVN_DAV_PROP_NS_CUSTOM;
1521 svn_ra_serf__set_prop(proppatch_ctx->changed_props,
1522 proppatch_ctx->path,
1523 ns, name, value, proppatch_ctx->pool);
1526 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
1531 return SVN_NO_ERROR;
1534 static svn_error_t *
1535 delete_entry(const char *path,
1536 svn_revnum_t revision,
1540 dir_context_t *dir = parent_baton;
1541 delete_context_t *delete_ctx;
1542 svn_ra_serf__handler_t *handler;
1543 const char *delete_target;
1546 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1548 delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
1553 /* Ensure our directory has been checked out */
1554 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1555 delete_target = svn_path_url_add_component2(dir->working_url,
1556 svn_relpath_basename(path,
1561 /* DELETE our entry */
1562 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1563 delete_ctx->path = apr_pstrdup(pool, path);
1564 delete_ctx->revision = revision;
1565 delete_ctx->lock_token_hash = dir->commit->lock_tokens;
1566 delete_ctx->keep_locks = dir->commit->keep_locks;
1568 handler = apr_pcalloc(pool, sizeof(*handler));
1569 handler->handler_pool = pool;
1570 handler->session = dir->commit->session;
1571 handler->conn = dir->commit->conn;
1573 handler->response_handler = svn_ra_serf__expect_empty_body;
1574 handler->response_baton = handler;
1576 handler->header_delegate = setup_delete_headers;
1577 handler->header_delegate_baton = delete_ctx;
1579 handler->method = "DELETE";
1580 handler->path = delete_target;
1582 err = svn_ra_serf__context_run_one(handler, pool);
1585 (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
1586 err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
1587 err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
1588 err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
1590 svn_error_clear(err);
1592 /* An error has been registered on the connection. Reset the thing
1593 so that we can use it again. */
1594 serf_connection_reset(handler->conn->conn);
1596 handler->body_delegate = create_delete_body;
1597 handler->body_delegate_baton = delete_ctx;
1598 handler->body_type = "text/xml";
1600 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1607 /* 204 No Content: item successfully deleted */
1608 if (handler->sline.code != 204)
1610 return svn_error_trace(return_response_err(handler));
1613 svn_hash_sets(dir->commit->deleted_entries,
1614 apr_pstrdup(dir->commit->pool, path), (void *)1);
1616 return SVN_NO_ERROR;
1619 static svn_error_t *
1620 add_directory(const char *path,
1622 const char *copyfrom_path,
1623 svn_revnum_t copyfrom_revision,
1624 apr_pool_t *dir_pool,
1627 dir_context_t *parent = parent_baton;
1629 svn_ra_serf__handler_t *handler;
1630 apr_status_t status;
1631 const char *mkcol_target;
1633 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1635 dir->pool = dir_pool;
1636 dir->parent_dir = parent;
1637 dir->commit = parent->commit;
1639 dir->base_revision = SVN_INVALID_REVNUM;
1640 dir->copy_revision = copyfrom_revision;
1641 dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1642 dir->relpath = apr_pstrdup(dir->pool, path);
1643 dir->name = svn_relpath_basename(dir->relpath, NULL);
1644 dir->changed_props = apr_hash_make(dir->pool);
1645 dir->removed_props = apr_hash_make(dir->pool);
1647 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1649 dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1651 mkcol_target = dir->url;
1655 /* Ensure our parent is checked out. */
1656 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1658 dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
1659 dir->name, dir->pool);
1660 mkcol_target = svn_path_url_add_component2(
1661 parent->working_url,
1662 dir->name, dir->pool);
1665 handler = apr_pcalloc(dir->pool, sizeof(*handler));
1666 handler->handler_pool = dir->pool;
1667 handler->conn = dir->commit->conn;
1668 handler->session = dir->commit->session;
1670 handler->response_handler = svn_ra_serf__expect_empty_body;
1671 handler->response_baton = handler;
1672 if (!dir->copy_path)
1674 handler->method = "MKCOL";
1675 handler->path = mkcol_target;
1680 const char *req_url;
1682 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1685 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1686 _("Unable to parse URL '%s'"),
1690 /* ### conn==NULL for session->conns[0]. same as commit->conn. */
1691 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1692 dir->commit->session,
1694 uri.path, dir->copy_revision,
1695 dir_pool, dir_pool));
1697 handler->method = "COPY";
1698 handler->path = req_url;
1700 handler->header_delegate = setup_copy_dir_headers;
1701 handler->header_delegate_baton = dir;
1704 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1706 switch (handler->sline.code)
1708 case 201: /* Created: item was successfully copied */
1709 case 204: /* No Content: item successfully replaced an existing target */
1713 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1714 _("Access to '%s' forbidden"),
1717 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1718 _("Adding directory failed: %s on %s "
1720 handler->method, handler->path,
1721 handler->sline.code, handler->sline.reason);
1726 return SVN_NO_ERROR;
1729 static svn_error_t *
1730 open_directory(const char *path,
1732 svn_revnum_t base_revision,
1733 apr_pool_t *dir_pool,
1736 dir_context_t *parent = parent_baton;
1739 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1741 dir->pool = dir_pool;
1743 dir->parent_dir = parent;
1744 dir->commit = parent->commit;
1747 dir->base_revision = base_revision;
1748 dir->relpath = apr_pstrdup(dir->pool, path);
1749 dir->name = svn_relpath_basename(dir->relpath, NULL);
1750 dir->changed_props = apr_hash_make(dir->pool);
1751 dir->removed_props = apr_hash_make(dir->pool);
1753 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1755 dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1760 SVN_ERR(get_version_url(&dir->url,
1761 dir->commit->session,
1762 dir->relpath, dir->base_revision,
1763 dir->commit->checked_in_url,
1764 dir->pool, dir->pool /* scratch_pool */));
1768 return SVN_NO_ERROR;
1771 static svn_error_t *
1772 change_dir_prop(void *dir_baton,
1774 const svn_string_t *value,
1777 dir_context_t *dir = dir_baton;
1779 const char *proppatch_target;
1782 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1784 proppatch_target = dir->url;
1788 /* Ensure we have a checked out dir. */
1789 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1791 proppatch_target = dir->working_url;
1794 name = apr_pstrdup(dir->pool, name);
1795 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1797 ns = SVN_DAV_PROP_NS_SVN;
1798 name += sizeof(SVN_PROP_PREFIX) - 1;
1802 ns = SVN_DAV_PROP_NS_CUSTOM;
1807 value = svn_string_dup(value, dir->pool);
1808 svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
1809 ns, name, value, dir->pool);
1813 value = svn_string_create_empty(dir->pool);
1814 svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
1815 ns, name, value, dir->pool);
1818 return SVN_NO_ERROR;
1821 static svn_error_t *
1822 close_directory(void *dir_baton,
1825 dir_context_t *dir = dir_baton;
1827 /* Huh? We're going to be called before the texts are sent. Ugh.
1828 * Therefore, just wave politely at our caller.
1831 /* PROPPATCH our prop change and pass it along. */
1832 if (apr_hash_count(dir->changed_props) ||
1833 apr_hash_count(dir->removed_props))
1835 proppatch_context_t *proppatch_ctx;
1837 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1838 proppatch_ctx->pool = pool;
1839 proppatch_ctx->commit = dir->commit;
1840 proppatch_ctx->relpath = dir->relpath;
1841 proppatch_ctx->changed_props = dir->changed_props;
1842 proppatch_ctx->removed_props = dir->removed_props;
1843 proppatch_ctx->base_revision = dir->base_revision;
1845 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1847 proppatch_ctx->path = dir->url;
1851 proppatch_ctx->path = dir->working_url;
1854 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
1857 return SVN_NO_ERROR;
1860 static svn_error_t *
1861 add_file(const char *path,
1863 const char *copy_path,
1864 svn_revnum_t copy_revision,
1865 apr_pool_t *file_pool,
1868 dir_context_t *dir = parent_baton;
1869 file_context_t *new_file;
1870 const char *deleted_parent = path;
1872 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1873 new_file->pool = file_pool;
1877 new_file->parent_dir = dir;
1878 new_file->commit = dir->commit;
1879 new_file->relpath = apr_pstrdup(new_file->pool, path);
1880 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1881 new_file->added = TRUE;
1882 new_file->base_revision = SVN_INVALID_REVNUM;
1883 new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1884 new_file->copy_revision = copy_revision;
1885 new_file->changed_props = apr_hash_make(new_file->pool);
1886 new_file->removed_props = apr_hash_make(new_file->pool);
1888 /* Ensure that the file doesn't exist by doing a HEAD on the
1889 resource. If we're using HTTP v2, we'll just look into the
1890 transaction root tree for this thing. */
1891 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1893 new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
1894 path, new_file->pool);
1898 /* Ensure our parent directory has been checked out */
1899 SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
1902 svn_path_url_add_component2(dir->working_url,
1903 new_file->name, new_file->pool);
1906 while (deleted_parent && deleted_parent[0] != '\0')
1908 if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
1912 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1915 if (! ((dir->added && !dir->copy_path) ||
1916 (deleted_parent && deleted_parent[0] != '\0')))
1918 svn_ra_serf__handler_t *handler;
1920 handler = apr_pcalloc(new_file->pool, sizeof(*handler));
1921 handler->handler_pool = new_file->pool;
1922 handler->session = new_file->commit->session;
1923 handler->conn = new_file->commit->conn;
1924 handler->method = "HEAD";
1925 handler->path = svn_path_url_add_component2(
1926 dir->commit->session->session_url.path,
1927 path, new_file->pool);
1928 handler->response_handler = svn_ra_serf__expect_empty_body;
1929 handler->response_baton = handler;
1931 SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
1933 if (handler->sline.code != 404)
1935 if (handler->sline.code != 200)
1939 err = svn_ra_serf__error_on_status(handler->sline,
1946 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1947 _("File '%s' already exists"), path);
1951 *file_baton = new_file;
1953 return SVN_NO_ERROR;
1956 static svn_error_t *
1957 open_file(const char *path,
1959 svn_revnum_t base_revision,
1960 apr_pool_t *file_pool,
1963 dir_context_t *parent = parent_baton;
1964 file_context_t *new_file;
1966 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1967 new_file->pool = file_pool;
1969 parent->ref_count++;
1971 new_file->parent_dir = parent;
1972 new_file->commit = parent->commit;
1973 new_file->relpath = apr_pstrdup(new_file->pool, path);
1974 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1975 new_file->added = FALSE;
1976 new_file->base_revision = base_revision;
1977 new_file->changed_props = apr_hash_make(new_file->pool);
1978 new_file->removed_props = apr_hash_make(new_file->pool);
1980 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
1982 new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1983 path, new_file->pool);
1987 /* CHECKOUT the file into our activity. */
1988 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1990 new_file->url = new_file->working_url;
1993 *file_baton = new_file;
1995 return SVN_NO_ERROR;
1998 static svn_error_t *
1999 apply_textdelta(void *file_baton,
2000 const char *base_checksum,
2002 svn_txdelta_window_handler_t *handler,
2003 void **handler_baton)
2005 file_context_t *ctx = file_baton;
2007 /* Store the stream in a temporary file; we'll give it to serf when we
2010 * TODO: There should be a way we can stream the request body instead of
2011 * writing to a temporary file (ugh). A special svn stream serf bucket
2012 * that returns EAGAIN until we receive the done call? But, when
2013 * would we run through the serf context? Grr.
2015 * ctx->pool is the same for all files in the commit that send a
2016 * textdelta so this file is explicitly closed in close_file to
2017 * avoid too many simultaneously open files.
2020 SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
2021 svn_io_file_del_on_pool_cleanup,
2024 ctx->stream = svn_stream_create(ctx, pool);
2025 svn_stream_set_write(ctx->stream, svndiff_stream_write);
2027 svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
2028 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
2031 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
2033 return SVN_NO_ERROR;
2036 static svn_error_t *
2037 change_file_prop(void *file_baton,
2039 const svn_string_t *value,
2042 file_context_t *file = file_baton;
2045 name = apr_pstrdup(file->pool, name);
2047 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2049 ns = SVN_DAV_PROP_NS_SVN;
2050 name += sizeof(SVN_PROP_PREFIX) - 1;
2054 ns = SVN_DAV_PROP_NS_CUSTOM;
2059 value = svn_string_dup(value, file->pool);
2060 svn_ra_serf__set_prop(file->changed_props, file->url,
2061 ns, name, value, file->pool);
2065 value = svn_string_create_empty(file->pool);
2067 svn_ra_serf__set_prop(file->removed_props, file->url,
2068 ns, name, value, file->pool);
2071 return SVN_NO_ERROR;
2074 static svn_error_t *
2075 close_file(void *file_baton,
2076 const char *text_checksum,
2077 apr_pool_t *scratch_pool)
2079 file_context_t *ctx = file_baton;
2080 svn_boolean_t put_empty_file = FALSE;
2081 apr_status_t status;
2083 ctx->result_checksum = text_checksum;
2087 svn_ra_serf__handler_t *handler;
2089 const char *req_url;
2091 status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
2094 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2095 _("Unable to parse URL '%s'"),
2099 /* ### conn==NULL for session->conns[0]. same as commit->conn. */
2100 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
2101 ctx->commit->session,
2103 uri.path, ctx->copy_revision,
2104 scratch_pool, scratch_pool));
2106 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2107 handler->handler_pool = scratch_pool;
2108 handler->method = "COPY";
2109 handler->path = req_url;
2110 handler->conn = ctx->commit->conn;
2111 handler->session = ctx->commit->session;
2113 handler->response_handler = svn_ra_serf__expect_empty_body;
2114 handler->response_baton = handler;
2116 handler->header_delegate = setup_copy_file_headers;
2117 handler->header_delegate_baton = ctx;
2119 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2121 if (handler->sline.code != 201 && handler->sline.code != 204)
2123 return svn_error_trace(return_response_err(handler));
2127 /* If we got no stream of changes, but this is an added-without-history
2128 * file, make a note that we'll be PUTting a zero-byte file to the server.
2130 if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
2131 put_empty_file = TRUE;
2133 /* If we had a stream of changes, push them to the server... */
2134 if (ctx->stream || put_empty_file)
2136 svn_ra_serf__handler_t *handler;
2138 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2139 handler->handler_pool = scratch_pool;
2140 handler->method = "PUT";
2141 handler->path = ctx->url;
2142 handler->conn = ctx->commit->conn;
2143 handler->session = ctx->commit->session;
2145 handler->response_handler = svn_ra_serf__expect_empty_body;
2146 handler->response_baton = handler;
2150 handler->body_delegate = create_empty_put_body;
2151 handler->body_delegate_baton = ctx;
2152 handler->body_type = "text/plain";
2156 handler->body_delegate = create_put_body;
2157 handler->body_delegate_baton = ctx;
2158 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2161 handler->header_delegate = setup_put_headers;
2162 handler->header_delegate_baton = ctx;
2164 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2166 if (handler->sline.code != 204 && handler->sline.code != 201)
2168 return svn_error_trace(return_response_err(handler));
2173 SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2175 /* If we had any prop changes, push them via PROPPATCH. */
2176 if (apr_hash_count(ctx->changed_props) ||
2177 apr_hash_count(ctx->removed_props))
2179 proppatch_context_t *proppatch;
2181 proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
2182 proppatch->pool = ctx->pool;
2183 proppatch->relpath = ctx->relpath;
2184 proppatch->path = ctx->url;
2185 proppatch->commit = ctx->commit;
2186 proppatch->changed_props = ctx->changed_props;
2187 proppatch->removed_props = ctx->removed_props;
2188 proppatch->base_revision = ctx->base_revision;
2190 SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
2193 return SVN_NO_ERROR;
2196 static svn_error_t *
2197 close_edit(void *edit_baton,
2200 commit_context_t *ctx = edit_baton;
2201 const char *merge_target =
2202 ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2203 const svn_commit_info_t *commit_info;
2206 /* MERGE our activity */
2207 SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
2209 ctx->session->conns[0],
2215 if (response_code != 200)
2217 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2218 _("MERGE request failed: returned %d "
2223 /* Inform the WC that we did a commit. */
2225 SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
2227 /* If we're using activities, DELETE our completed activity. */
2228 if (ctx->activity_url)
2230 svn_ra_serf__handler_t *handler;
2232 handler = apr_pcalloc(pool, sizeof(*handler));
2233 handler->handler_pool = pool;
2234 handler->method = "DELETE";
2235 handler->path = ctx->activity_url;
2236 handler->conn = ctx->conn;
2237 handler->session = ctx->session;
2239 handler->response_handler = svn_ra_serf__expect_empty_body;
2240 handler->response_baton = handler;
2242 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2244 SVN_ERR_ASSERT(handler->sline.code == 204);
2247 return SVN_NO_ERROR;
2250 static svn_error_t *
2251 abort_edit(void *edit_baton,
2254 commit_context_t *ctx = edit_baton;
2255 svn_ra_serf__handler_t *handler;
2257 /* If an activity or transaction wasn't even created, don't bother
2258 trying to delete it. */
2259 if (! (ctx->activity_url || ctx->txn_url))
2260 return SVN_NO_ERROR;
2262 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2263 had a problem. We need to reset it, in order to use it again. */
2264 serf_connection_reset(ctx->session->conns[0]->conn);
2266 /* DELETE our aborted activity */
2267 handler = apr_pcalloc(pool, sizeof(*handler));
2268 handler->handler_pool = pool;
2269 handler->method = "DELETE";
2270 handler->conn = ctx->session->conns[0];
2271 handler->session = ctx->session;
2273 handler->response_handler = svn_ra_serf__expect_empty_body;
2274 handler->response_baton = handler;
2276 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2277 handler->path = ctx->txn_url;
2279 handler->path = ctx->activity_url;
2281 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2284 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2285 404 if the activity wasn't found. */
2286 if (handler->sline.code != 204
2287 && handler->sline.code != 403
2288 && handler->sline.code != 404
2291 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2292 _("DELETE returned unexpected status: %d"),
2293 handler->sline.code);
2296 return SVN_NO_ERROR;
2300 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2301 const svn_delta_editor_t **ret_editor,
2303 apr_hash_t *revprop_table,
2304 svn_commit_callback2_t callback,
2305 void *callback_baton,
2306 apr_hash_t *lock_tokens,
2307 svn_boolean_t keep_locks,
2310 svn_ra_serf__session_t *session = ra_session->priv;
2311 svn_delta_editor_t *editor;
2312 commit_context_t *ctx;
2313 const char *repos_root;
2314 const char *base_relpath;
2315 svn_boolean_t supports_ephemeral_props;
2317 ctx = apr_pcalloc(pool, sizeof(*ctx));
2321 ctx->session = session;
2322 ctx->conn = session->conns[0];
2324 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2326 /* If the server supports ephemeral properties, add some carrying
2327 interesting version information. */
2328 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2329 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2331 if (supports_ephemeral_props)
2333 svn_hash_sets(ctx->revprop_table,
2334 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2335 svn_string_create(SVN_VER_NUMBER, pool));
2336 svn_hash_sets(ctx->revprop_table,
2337 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2338 svn_string_create(session->useragent, pool));
2341 ctx->callback = callback;
2342 ctx->callback_baton = callback_baton;
2344 ctx->lock_tokens = lock_tokens;
2345 ctx->keep_locks = keep_locks;
2347 ctx->deleted_entries = apr_hash_make(ctx->pool);
2349 editor = svn_delta_default_editor(pool);
2350 editor->open_root = open_root;
2351 editor->delete_entry = delete_entry;
2352 editor->add_directory = add_directory;
2353 editor->open_directory = open_directory;
2354 editor->change_dir_prop = change_dir_prop;
2355 editor->close_directory = close_directory;
2356 editor->add_file = add_file;
2357 editor->open_file = open_file;
2358 editor->apply_textdelta = apply_textdelta;
2359 editor->change_file_prop = change_file_prop;
2360 editor->close_file = close_file;
2361 editor->close_edit = close_edit;
2362 editor->abort_edit = abort_edit;
2364 *ret_editor = editor;
2367 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2368 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2371 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2372 *edit_baton, repos_root, base_relpath,
2373 session->shim_callbacks, pool, pool));
2375 return SVN_NO_ERROR;
2379 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2382 const svn_string_t *const *old_value_p,
2383 const svn_string_t *value,
2386 svn_ra_serf__session_t *session = ra_session->priv;
2387 proppatch_context_t *proppatch_ctx;
2388 commit_context_t *commit;
2389 const char *proppatch_target;
2395 svn_boolean_t capable;
2396 SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
2397 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2400 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2401 SVN_ERR_ASSERT(capable);
2404 commit = apr_pcalloc(pool, sizeof(*commit));
2406 commit->pool = pool;
2408 commit->session = session;
2409 commit->conn = session->conns[0];
2411 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2413 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2417 const char *vcc_url;
2419 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
2420 commit->conn, pool));
2422 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2423 commit->conn, vcc_url, rev,
2428 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2430 ns = SVN_DAV_PROP_NS_SVN;
2431 name += sizeof(SVN_PROP_PREFIX) - 1;
2435 ns = SVN_DAV_PROP_NS_CUSTOM;
2438 /* PROPPATCH our log message and pass it along. */
2439 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2440 proppatch_ctx->pool = pool;
2441 proppatch_ctx->commit = commit;
2442 proppatch_ctx->path = proppatch_target;
2443 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
2444 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
2447 proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
2448 proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
2450 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2452 if (old_value_p && *old_value_p)
2454 svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
2455 proppatch_ctx->path,
2456 ns, name, *old_value_p, proppatch_ctx->pool);
2458 else if (old_value_p)
2460 svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
2462 svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
2463 proppatch_ctx->path,
2464 ns, name, dummy_value, proppatch_ctx->pool);
2469 svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
2470 ns, name, value, proppatch_ctx->pool);
2474 value = svn_string_create_empty(proppatch_ctx->pool);
2476 svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
2477 ns, name, value, proppatch_ctx->pool);
2480 err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
2484 (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
2485 _("DAV request failed; it's possible that the repository's "
2486 "pre-revprop-change hook either failed or is non-existent"));
2488 return SVN_NO_ERROR;