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 commit_context_t *commit;
109 /* Represents a directory. */
110 typedef struct dir_context_t {
111 /* Pool for our directory. */
114 /* The root commit we're in progress for. */
115 commit_context_t *commit;
117 /* URL to operate against (used for CHECKOUT and PROPPATCH before
118 HTTP v2, for PROPPATCH in HTTP v2). */
121 /* How many pending changes we have left in this directory. */
122 unsigned int ref_count;
124 /* Is this directory being added? (Otherwise, just opened.) */
128 struct dir_context_t *parent_dir;
130 /* The directory name; if "", we're the 'root' */
133 /* The basename of the directory. "" for the 'root' */
136 /* The base revision of the dir. */
137 svn_revnum_t base_revision;
139 const char *copy_path;
140 svn_revnum_t copy_revision;
142 /* Changed and removed properties */
143 apr_hash_t *changed_props;
144 apr_hash_t *removed_props;
146 /* The checked-out working resource for this directory. May be NULL; if so
147 call checkout_dir() first. */
148 const char *working_url;
151 /* Represents a file to be committed. */
152 typedef struct file_context_t {
153 /* Pool for our file. */
156 /* The root commit we're in progress for. */
157 commit_context_t *commit;
159 /* Is this file being added? (Otherwise, just opened.) */
162 dir_context_t *parent_dir;
167 /* The checked-out working resource for this file. */
168 const char *working_url;
170 /* The base revision of the file. */
171 svn_revnum_t base_revision;
173 /* Copy path and revision */
174 const char *copy_path;
175 svn_revnum_t copy_revision;
178 svn_stream_t *stream;
180 /* Temporary file containing the svndiff. */
183 /* Our base checksum as reported by the WC. */
184 const char *base_checksum;
186 /* Our resulting checksum as reported by the WC. */
187 const char *result_checksum;
189 /* Changed and removed properties. */
190 apr_hash_t *changed_props;
191 apr_hash_t *removed_props;
193 /* URL to PUT the file at. */
199 /* Setup routines and handlers for various requests we'll invoke. */
202 return_response_err(svn_ra_serf__handler_t *handler)
206 /* We should have captured SLINE and LOCATION in the HANDLER. */
207 SVN_ERR_ASSERT(handler->handler_pool != NULL);
209 /* Ye Olde Fallback Error */
210 err = svn_error_compose_create(
211 handler->server_error != NULL
212 ? handler->server_error->error
214 svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
215 _("%s of '%s': %d %s"),
216 handler->method, handler->path,
217 handler->sline.code, handler->sline.reason));
219 /* Try to return one of the standard errors for 301, 404, etc.,
220 then look for an error embedded in the response. */
221 return svn_error_compose_create(svn_ra_serf__error_on_status(
228 /* Implements svn_ra_serf__request_body_delegate_t */
230 create_checkout_body(serf_bucket_t **bkt,
232 serf_bucket_alloc_t *alloc,
235 const char *activity_url = baton;
236 serf_bucket_t *body_bkt;
238 body_bkt = serf_bucket_aggregate_create(alloc);
240 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
241 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
244 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
245 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
247 SVN_ERR_ASSERT(activity_url != NULL);
248 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
250 strlen(activity_url));
252 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
253 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
254 svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc);
255 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
262 /* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
263 given COMMIT_CTX. The resulting working resource will be returned in
264 *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
265 are performed in SCRATCH_POOL.
267 ### are these URLs actually repos relpath values? or fspath? or maybe
268 ### the abspath portion of the full URL.
270 This function operates synchronously.
272 Strictly speaking, we could perform "all" of the CHECKOUT requests
273 when the commit starts, and only block when we need a specific
274 answer. Or, at a minimum, send off these individual requests async
275 and block when we need the answer (eg PUT or PROPPATCH).
277 However: the investment to speed this up is not worthwhile, given
278 that CHECKOUT (and the related round trip) is completely obviated
282 checkout_node(const char **working_url,
283 const commit_context_t *commit_ctx,
284 const char *node_url,
285 apr_pool_t *result_pool,
286 apr_pool_t *scratch_pool)
288 svn_ra_serf__handler_t handler = { 0 };
292 /* HANDLER_POOL is the scratch pool since we don't need to remember
293 anything from the handler. We just want the working resource. */
294 handler.handler_pool = scratch_pool;
295 handler.session = commit_ctx->session;
296 handler.conn = commit_ctx->conn;
298 handler.body_delegate = create_checkout_body;
299 handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
300 handler.body_type = "text/xml";
302 handler.response_handler = svn_ra_serf__expect_empty_body;
303 handler.response_baton = &handler;
305 handler.method = "CHECKOUT";
306 handler.path = node_url;
308 SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
310 if (handler.sline.code != 201)
311 return svn_error_trace(return_response_err(&handler));
313 if (handler.location == NULL)
314 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
315 _("No Location header received"));
317 /* We only want the path portion of the Location header.
318 (code.google.com sometimes returns an 'http:' scheme for an
319 'https:' transaction ... we'll work around that by stripping the
320 scheme, host, and port here and re-adding the correct ones
322 status = apr_uri_parse(scratch_pool, handler.location, &uri);
324 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
325 _("Error parsing Location header value"));
327 *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
333 /* This is a wrapper around checkout_node() (which see for
334 documentation) which simply retries the CHECKOUT request when it
335 fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
338 See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
342 retry_checkout_node(const char **working_url,
343 const commit_context_t *commit_ctx,
344 const char *node_url,
345 apr_pool_t *result_pool,
346 apr_pool_t *scratch_pool)
348 svn_error_t *err = SVN_NO_ERROR;
349 int retry_count = 5; /* Magic, arbitrary number. */
353 svn_error_clear(err);
355 err = checkout_node(working_url, commit_ctx, node_url,
356 result_pool, scratch_pool);
358 /* There's a small chance of a race condition here if Apache is
359 experiencing heavy commit concurrency or if the network has
360 long latency. It's possible that the value of HEAD changed
361 between the time we fetched the latest baseline and the time
362 we try to CHECKOUT that baseline. If that happens, Apache
363 will throw us a BAD_BASELINE error (deltaV says you can only
364 checkout the latest baseline). We just ignore that specific
365 error and retry a few times, asking for the latest baseline
367 if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
370 while (err && retry_count--);
377 checkout_dir(dir_context_t *dir,
378 apr_pool_t *scratch_pool)
381 dir_context_t *p_dir = dir;
382 const char *checkout_url;
383 const char **working;
385 if (dir->working_url)
390 /* Is this directory or one of our parent dirs newly added?
391 * If so, we're already implicitly checked out. */
396 /* Calculate the working_url by skipping the shared ancestor bewteen
397 * the parent->relpath and dir->relpath. This is safe since an
398 * add is guaranteed to have a parent that is checked out. */
399 dir_context_t *parent = p_dir->parent_dir;
400 const char *relpath = svn_relpath_skip_ancestor(parent->relpath,
403 /* Implicitly checkout this dir now. */
404 SVN_ERR_ASSERT(parent->working_url);
405 dir->working_url = svn_path_url_add_component2(
410 p_dir = p_dir->parent_dir;
413 /* We could be called twice for the root: once to checkout the baseline;
414 * once to checkout the directory itself if we need to do so.
415 * Note: CHECKOUT_URL should live longer than HANDLER.
417 if (!dir->parent_dir && !dir->commit->baseline_url)
419 checkout_url = dir->commit->vcc_url;
420 working = &dir->commit->baseline_url;
424 checkout_url = dir->url;
425 working = &dir->working_url;
428 /* Checkout our directory into the activity URL now. */
429 err = retry_checkout_node(working, dir->commit, checkout_url,
430 dir->pool, scratch_pool);
433 if (err->apr_err == SVN_ERR_FS_CONFLICT)
434 SVN_ERR_W(err, apr_psprintf(scratch_pool,
435 _("Directory '%s' is out of date; try updating"),
436 svn_dirent_local_style(dir->relpath, scratch_pool)));
444 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
445 * RELPATH (relative to the root of SESSION).
447 * Try to find this version url in three ways:
448 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
449 * version url from the working copy properties.
450 * Second, if the version url of the parent directory PARENT_VSN_URL is
451 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
453 * Else, fetch the version url for the root of SESSION using CONN and
454 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
457 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
458 * temporary allocation.
461 get_version_url(const char **checked_in_url,
462 svn_ra_serf__session_t *session,
464 svn_revnum_t base_revision,
465 const char *parent_vsn_url,
466 apr_pool_t *result_pool,
467 apr_pool_t *scratch_pool)
469 const char *root_checkout;
471 if (session->wc_callbacks->get_wc_prop)
473 const svn_string_t *current_version;
475 SVN_ERR(session->wc_callbacks->get_wc_prop(
476 session->wc_callback_baton,
478 SVN_RA_SERF__WC_CHECKED_IN_URL,
479 ¤t_version, scratch_pool));
484 svn_urlpath__canonicalize(current_version->data, result_pool);
491 root_checkout = parent_vsn_url;
495 const char *propfind_url;
496 svn_ra_serf__connection_t *conn = session->conns[0];
498 if (SVN_IS_VALID_REVNUM(base_revision))
500 /* mod_dav_svn can't handle the "Label:" header that
501 svn_ra_serf__deliver_props() is going to try to use for
502 this lookup, so we'll do things the hard(er) way, by
503 looking up the version URL from a resource in the
504 baseline collection. */
505 /* ### conn==NULL for session->conns[0]. same as CONN. */
506 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
507 NULL /* latest_revnum */,
508 session, NULL /* conn */,
509 NULL /* url */, base_revision,
510 scratch_pool, scratch_pool));
514 propfind_url = session->session_url.path;
517 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
518 conn, propfind_url, base_revision,
520 scratch_pool, scratch_pool));
522 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
523 _("Path '%s' not present"),
524 session->session_url.path);
526 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
529 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
536 checkout_file(file_context_t *file,
537 apr_pool_t *scratch_pool)
540 dir_context_t *parent_dir = file->parent_dir;
541 const char *checkout_url;
543 /* Is one of our parent dirs newly added? If so, we're already
544 * implicitly checked out.
548 if (parent_dir->added)
550 /* Implicitly checkout this file now. */
551 file->working_url = svn_path_url_add_component2(
552 parent_dir->working_url,
553 svn_relpath_skip_ancestor(
554 parent_dir->relpath, file->relpath),
558 parent_dir = parent_dir->parent_dir;
561 SVN_ERR(get_version_url(&checkout_url,
562 file->commit->session,
563 file->relpath, file->base_revision,
564 NULL, scratch_pool, scratch_pool));
566 /* Checkout our file into the activity URL now. */
567 err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
568 file->pool, scratch_pool);
571 if (err->apr_err == SVN_ERR_FS_CONFLICT)
572 SVN_ERR_W(err, apr_psprintf(scratch_pool,
573 _("File '%s' is out of date; try updating"),
574 svn_dirent_local_style(file->relpath, scratch_pool)));
581 /* Helper function for proppatch_walker() below. */
583 get_encoding_and_cdata(const char **encoding_p,
584 const svn_string_t **encoded_value_p,
585 serf_bucket_alloc_t *alloc,
586 const svn_string_t *value,
587 apr_pool_t *result_pool,
588 apr_pool_t *scratch_pool)
593 *encoded_value_p = NULL;
597 /* If a property is XML-safe, XML-encode it. Else, base64-encode
599 if (svn_xml_is_xml_safe(value->data, value->len))
601 svn_stringbuf_t *xml_esc = NULL;
602 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
604 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
608 *encoding_p = "base64";
609 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
615 typedef struct walker_baton_t {
616 serf_bucket_t *body_bkt;
617 apr_pool_t *body_pool;
619 apr_hash_t *previous_changed_props;
620 apr_hash_t *previous_removed_props;
624 /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
625 rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */
628 filter_props_with_old_value,
629 filter_props_without_old_value
632 /* Is the property being deleted? */
633 svn_boolean_t deleting;
636 /* If we have (recorded in WB) the old value of the property named NS:NAME,
637 * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
638 * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */
640 derive_old_val(svn_boolean_t *have_old_val,
641 const svn_string_t **old_val_p,
646 *have_old_val = FALSE;
648 if (wb->previous_changed_props)
650 const svn_string_t *val;
651 val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
655 *have_old_val = TRUE;
660 if (wb->previous_removed_props)
662 const svn_string_t *val;
663 val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
667 *have_old_val = TRUE;
676 proppatch_walker(void *baton,
679 const svn_string_t *val,
680 apr_pool_t *scratch_pool)
682 walker_baton_t *wb = baton;
683 serf_bucket_t *body_bkt = wb->body_bkt;
684 serf_bucket_t *cdata_bkt;
685 serf_bucket_alloc_t *alloc;
686 const char *encoding;
687 svn_boolean_t have_old_val;
688 const svn_string_t *old_val;
689 const svn_string_t *encoded_value;
690 const char *prop_name;
692 SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
694 /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
696 if (wb->filter != filter_all_props)
698 if (wb->filter == filter_props_with_old_value && ! have_old_val)
700 if (wb->filter == filter_props_without_old_value && have_old_val)
706 alloc = body_bkt->allocator;
708 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
709 wb->body_pool, scratch_pool));
712 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
721 /* Use the namespace prefix instead of adding the xmlns attribute to support
722 property names containing ':' */
723 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
724 prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
725 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
726 prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
729 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
730 "V:encoding", encoding,
733 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
734 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
739 const char *encoding2;
740 const svn_string_t *encoded_value2;
741 serf_bucket_t *cdata_bkt2;
743 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
745 wb->body_pool, scratch_pool));
749 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
759 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
760 "V:" SVN_DAV__OLD_VALUE,
761 "V:encoding", encoding2,
764 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
765 "V:" SVN_DAV__OLD_VALUE,
766 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
770 serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
772 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
773 "V:" SVN_DAV__OLD_VALUE);
776 serf_bucket_aggregate_append(body_bkt, cdata_bkt);
777 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
782 /* Possible add the lock-token "If:" precondition header to HEADERS if
783 an examination of COMMIT_CTX and RELPATH indicates that this is the
786 Generally speaking, if the client provided a lock token for
787 RELPATH, it's the right thing to do. There is a notable instance
788 where this is not the case, however. If the file at RELPATH was
789 explicitly deleted in this commit already, then mod_dav removed its
790 lock token when it fielded the DELETE request, so we don't want to
791 set the lock precondition again. (See
792 http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
795 maybe_set_lock_token_header(serf_bucket_t *headers,
796 commit_context_t *commit_ctx,
802 if (! (relpath && commit_ctx->lock_tokens))
805 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
807 token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
810 const char *token_header;
811 const char *token_uri;
812 apr_uri_t uri = commit_ctx->session->session_url;
814 /* Supplying the optional URI affects apache response when
815 the lock is broken, see issue 4369. When present any URI
816 must be absolute (RFC 2518 9.4). */
817 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
819 token_uri = apr_uri_unparse(pool, &uri, 0);
821 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
823 serf_bucket_headers_set(headers, "If", token_header);
831 setup_proppatch_headers(serf_bucket_t *headers,
835 proppatch_context_t *proppatch = baton;
837 if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
839 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
840 apr_psprintf(pool, "%ld",
841 proppatch->base_revision));
844 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
845 proppatch->relpath, pool));
851 struct proppatch_body_baton_t {
852 proppatch_context_t *proppatch;
854 /* Content in the body should be allocated here, to live long enough. */
855 apr_pool_t *body_pool;
858 /* Implements svn_ra_serf__request_body_delegate_t */
860 create_proppatch_body(serf_bucket_t **bkt,
862 serf_bucket_alloc_t *alloc,
863 apr_pool_t *scratch_pool)
865 struct proppatch_body_baton_t *pbb = baton;
866 proppatch_context_t *ctx = pbb->proppatch;
867 serf_bucket_t *body_bkt;
868 walker_baton_t wb = { 0 };
870 body_bkt = serf_bucket_aggregate_create(alloc);
872 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
873 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
875 "xmlns:V", SVN_DAV_PROP_NS_DAV,
876 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
877 "xmlns:S", SVN_DAV_PROP_NS_SVN,
880 wb.body_bkt = body_bkt;
881 wb.body_pool = pbb->body_pool;
882 wb.previous_changed_props = ctx->previous_changed_props;
883 wb.previous_removed_props = ctx->previous_removed_props;
886 if (apr_hash_count(ctx->changed_props) > 0)
888 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
889 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
891 wb.filter = filter_all_props;
893 SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
895 proppatch_walker, &wb,
898 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
899 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
902 if (apr_hash_count(ctx->removed_props) > 0)
904 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
905 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
907 wb.filter = filter_props_with_old_value;
909 SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
911 proppatch_walker, &wb,
914 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
915 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
918 if (apr_hash_count(ctx->removed_props) > 0)
920 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
921 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
923 wb.filter = filter_props_without_old_value;
925 SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
927 proppatch_walker, &wb,
930 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
931 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
934 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
941 proppatch_resource(proppatch_context_t *proppatch,
942 commit_context_t *commit,
945 svn_ra_serf__handler_t *handler;
946 struct proppatch_body_baton_t pbb;
948 handler = apr_pcalloc(pool, sizeof(*handler));
949 handler->handler_pool = pool;
950 handler->method = "PROPPATCH";
951 handler->path = proppatch->path;
952 handler->conn = commit->conn;
953 handler->session = commit->session;
955 handler->header_delegate = setup_proppatch_headers;
956 handler->header_delegate_baton = proppatch;
958 pbb.proppatch = proppatch;
959 pbb.body_pool = pool;
960 handler->body_delegate = create_proppatch_body;
961 handler->body_delegate_baton = &pbb;
963 handler->response_handler = svn_ra_serf__handle_multistatus_only;
964 handler->response_baton = handler;
966 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
968 if (handler->sline.code != 207
969 || (handler->server_error != NULL
970 && handler->server_error->error != NULL))
972 return svn_error_create(
973 SVN_ERR_RA_DAV_PROPPATCH_FAILED,
974 return_response_err(handler),
975 _("At least one property change failed; repository"
982 /* Implements svn_ra_serf__request_body_delegate_t */
984 create_put_body(serf_bucket_t **body_bkt,
986 serf_bucket_alloc_t *alloc,
989 file_context_t *ctx = baton;
992 /* We need to flush the file, make it unbuffered (so that it can be
993 * zero-copied via mmap), and reset the position before attempting to
996 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
997 * and zero-copy the PUT body. However, on older APR versions, we can't
998 * check the buffer status; but serf will fall through and create a file
999 * bucket for us on the buffered svndiff handle.
1001 apr_file_flush(ctx->svndiff);
1002 #if APR_VERSION_AT_LEAST(1, 3, 0)
1003 apr_file_buffer_set(ctx->svndiff, NULL, 0);
1006 apr_file_seek(ctx->svndiff, APR_SET, &offset);
1008 *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
1009 return SVN_NO_ERROR;
1012 /* Implements svn_ra_serf__request_body_delegate_t */
1013 static svn_error_t *
1014 create_empty_put_body(serf_bucket_t **body_bkt,
1016 serf_bucket_alloc_t *alloc,
1019 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
1020 return SVN_NO_ERROR;
1023 static svn_error_t *
1024 setup_put_headers(serf_bucket_t *headers,
1028 file_context_t *ctx = baton;
1030 if (SVN_IS_VALID_REVNUM(ctx->base_revision))
1032 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1033 apr_psprintf(pool, "%ld", ctx->base_revision));
1036 if (ctx->base_checksum)
1038 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
1039 ctx->base_checksum);
1042 if (ctx->result_checksum)
1044 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
1045 ctx->result_checksum);
1048 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
1049 ctx->relpath, pool));
1054 static svn_error_t *
1055 setup_copy_file_headers(serf_bucket_t *headers,
1059 file_context_t *file = baton;
1061 const char *absolute_uri;
1063 /* The Dest URI must be absolute. Bummer. */
1064 uri = file->commit->session->session_url;
1065 uri.path = (char*)file->url;
1066 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1068 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1070 serf_bucket_headers_setn(headers, "Depth", "0");
1071 serf_bucket_headers_setn(headers, "Overwrite", "T");
1073 return SVN_NO_ERROR;
1076 static svn_error_t *
1077 setup_if_header_recursive(svn_boolean_t *added,
1078 serf_bucket_t *headers,
1079 commit_context_t *commit_ctx,
1080 const char *rq_relpath,
1083 svn_stringbuf_t *sb = NULL;
1084 apr_hash_index_t *hi;
1085 apr_pool_t *iterpool = NULL;
1087 if (!commit_ctx->lock_tokens)
1090 return SVN_NO_ERROR;
1093 /* We try to create a directory, so within the Subversion world that
1094 would imply that there is nothing here, but mod_dav_svn still sees
1095 locks on the old nodes here as in DAV it is perfectly legal to lock
1096 something that is not there...
1098 Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
1099 the locks we know of with the request */
1101 for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
1103 hi = apr_hash_next(hi))
1105 const char *relpath = svn__apr_hash_index_key(hi);
1108 if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
1110 else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
1112 /* When a path is already explicit deleted then its lock
1113 will be removed by mod_dav. But mod_dav doesn't remove
1114 locks on descendants */
1119 iterpool = svn_pool_create(pool);
1121 svn_pool_clear(iterpool);
1124 sb = svn_stringbuf_create("", pool);
1126 svn_stringbuf_appendbyte(sb, ' ');
1128 uri = commit_ctx->session->session_url;
1129 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
1132 svn_stringbuf_appendbyte(sb, '<');
1133 svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1134 svn_stringbuf_appendcstr(sb, "> (<");
1135 svn_stringbuf_appendcstr(sb, svn__apr_hash_index_val(hi));
1136 svn_stringbuf_appendcstr(sb, ">)");
1140 svn_pool_destroy(iterpool);
1144 serf_bucket_headers_set(headers, "If", sb->data);
1150 return SVN_NO_ERROR;
1153 static svn_error_t *
1154 setup_add_dir_common_headers(serf_bucket_t *headers,
1158 dir_context_t *dir = baton;
1159 svn_boolean_t added;
1161 return svn_error_trace(
1162 setup_if_header_recursive(&added, headers, dir->commit, dir->relpath,
1166 static svn_error_t *
1167 setup_copy_dir_headers(serf_bucket_t *headers,
1171 dir_context_t *dir = baton;
1173 const char *absolute_uri;
1175 /* The Dest URI must be absolute. Bummer. */
1176 uri = dir->commit->session->session_url;
1178 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1180 uri.path = (char *)dir->url;
1184 uri.path = (char *)svn_path_url_add_component2(
1185 dir->parent_dir->working_url,
1188 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1190 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1192 serf_bucket_headers_setn(headers, "Depth", "infinity");
1193 serf_bucket_headers_setn(headers, "Overwrite", "T");
1195 /* Implicitly checkout this dir now. */
1196 dir->working_url = apr_pstrdup(dir->pool, uri.path);
1198 return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool));
1201 static svn_error_t *
1202 setup_delete_headers(serf_bucket_t *headers,
1206 delete_context_t *del = baton;
1207 svn_boolean_t added;
1209 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1210 apr_ltoa(pool, del->revision));
1212 SVN_ERR(setup_if_header_recursive(&added, headers, del->commit,
1213 del->relpath, pool));
1215 if (added && del->commit->keep_locks)
1216 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1217 SVN_DAV_OPTION_KEEP_LOCKS);
1219 return SVN_NO_ERROR;
1222 /* Helper function to write the svndiff stream to temporary file. */
1223 static svn_error_t *
1224 svndiff_stream_write(void *file_baton,
1228 file_context_t *ctx = file_baton;
1229 apr_status_t status;
1231 status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
1233 return svn_error_wrap_apr(status, _("Failed writing updated file"));
1235 return SVN_NO_ERROR;
1240 /* POST against 'me' resource handlers. */
1242 /* Implements svn_ra_serf__request_body_delegate_t */
1243 static svn_error_t *
1244 create_txn_post_body(serf_bucket_t **body_bkt,
1246 serf_bucket_alloc_t *alloc,
1249 apr_hash_t *revprops = baton;
1250 svn_skel_t *request_skel;
1251 svn_stringbuf_t *skel_str;
1253 request_skel = svn_skel__make_empty_list(pool);
1256 svn_skel_t *proplist_skel;
1258 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1259 svn_skel__prepend(proplist_skel, request_skel);
1260 svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1261 skel_str = svn_skel__unparse(request_skel, pool);
1262 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1266 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1269 return SVN_NO_ERROR;
1272 /* Implements svn_ra_serf__request_header_delegate_t */
1273 static svn_error_t *
1274 setup_post_headers(serf_bucket_t *headers,
1278 #ifdef SVN_DAV_SEND_VTXN_NAME
1279 /* Enable this to exercise the VTXN-NAME code based on a client
1280 supplied transaction name. */
1281 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1282 svn_uuid_generate(pool));
1285 return SVN_NO_ERROR;
1289 /* Handler baton for POST request. */
1290 typedef struct post_response_ctx_t
1292 svn_ra_serf__handler_t *handler;
1293 commit_context_t *commit_ctx;
1294 } post_response_ctx_t;
1297 /* This implements serf_bucket_headers_do_callback_fn_t. */
1299 post_headers_iterator_callback(void *baton,
1303 post_response_ctx_t *prc = baton;
1304 commit_context_t *prc_cc = prc->commit_ctx;
1305 svn_ra_serf__session_t *sess = prc_cc->session;
1307 /* If we provided a UUID to the POST request, we should get back
1308 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1309 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
1312 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1314 /* Build out txn and txn-root URLs using the txn name we're
1315 given, and store the whole lot of it in the commit context. */
1317 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1318 prc_cc->txn_root_url =
1319 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1322 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1324 /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1325 given, and store the whole lot of it in the commit context. */
1327 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1328 prc_cc->txn_root_url =
1329 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1336 /* A custom serf_response_handler_t which is mostly a wrapper around
1337 svn_ra_serf__expect_empty_body -- it just notices POST response
1340 Implements svn_ra_serf__response_handler_t */
1341 static svn_error_t *
1342 post_response_handler(serf_request_t *request,
1343 serf_bucket_t *response,
1345 apr_pool_t *scratch_pool)
1347 post_response_ctx_t *prc = baton;
1348 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1350 /* Then see which ones we can discover. */
1351 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1353 /* Execute the 'real' response handler to XML-parse the repsonse body. */
1354 return svn_ra_serf__expect_empty_body(request, response,
1355 prc->handler, scratch_pool);
1360 /* Commit baton callbacks */
1362 static svn_error_t *
1363 open_root(void *edit_baton,
1364 svn_revnum_t base_revision,
1365 apr_pool_t *dir_pool,
1368 commit_context_t *ctx = edit_baton;
1369 svn_ra_serf__handler_t *handler;
1370 proppatch_context_t *proppatch_ctx;
1372 apr_hash_index_t *hi;
1373 const char *proppatch_target = NULL;
1375 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
1377 post_response_ctx_t *prc;
1378 const char *rel_path;
1379 svn_boolean_t post_with_revprops
1380 = (NULL != svn_hash_gets(ctx->session->supported_posts,
1381 "create-txn-with-props"));
1383 /* Create our activity URL now on the server. */
1384 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1385 handler->handler_pool = ctx->pool;
1386 handler->method = "POST";
1387 handler->body_type = SVN_SKEL_MIME_TYPE;
1388 handler->body_delegate = create_txn_post_body;
1389 handler->body_delegate_baton =
1390 post_with_revprops ? ctx->revprop_table : NULL;
1391 handler->header_delegate = setup_post_headers;
1392 handler->header_delegate_baton = NULL;
1393 handler->path = ctx->session->me_resource;
1394 handler->conn = ctx->session->conns[0];
1395 handler->session = ctx->session;
1397 prc = apr_pcalloc(ctx->pool, sizeof(*prc));
1398 prc->handler = handler;
1399 prc->commit_ctx = ctx;
1401 handler->response_handler = post_response_handler;
1402 handler->response_baton = prc;
1404 SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1406 if (handler->sline.code != 201)
1408 apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1410 switch (handler->sline.code)
1413 status = SVN_ERR_RA_DAV_FORBIDDEN;
1416 status = SVN_ERR_FS_NOT_FOUND;
1420 return svn_error_createf(status, NULL,
1421 _("%s of '%s': %d %s (%s://%s)"),
1422 handler->method, handler->path,
1423 handler->sline.code, handler->sline.reason,
1424 ctx->session->session_url.scheme,
1425 ctx->session->session_url.hostinfo);
1427 if (! (ctx->txn_root_url && ctx->txn_url))
1429 return svn_error_createf(
1430 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1431 _("POST request did not return transaction information"));
1434 /* Fixup the txn_root_url to point to the anchor of the commit. */
1435 SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
1436 ctx->session->session_url.path,
1437 ctx->session, NULL, dir_pool));
1438 ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
1439 rel_path, ctx->pool);
1441 /* Build our directory baton. */
1442 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1443 dir->pool = dir_pool;
1445 dir->base_revision = base_revision;
1448 dir->changed_props = apr_hash_make(dir->pool);
1449 dir->removed_props = apr_hash_make(dir->pool);
1450 dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
1452 /* If we included our revprops in the POST, we need not
1454 proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
1458 const char *activity_str = ctx->session->activity_collection_url;
1461 SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
1462 ctx->session->conns[0],
1466 /* Cache the result. */
1469 ctx->session->activity_collection_url =
1470 apr_pstrdup(ctx->session->pool, activity_str);
1474 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1475 _("The OPTIONS response did not include the "
1476 "requested activity-collection-set value"));
1480 svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
1483 /* Create our activity URL now on the server. */
1484 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1485 handler->handler_pool = ctx->pool;
1486 handler->method = "MKACTIVITY";
1487 handler->path = ctx->activity_url;
1488 handler->conn = ctx->session->conns[0];
1489 handler->session = ctx->session;
1491 handler->response_handler = svn_ra_serf__expect_empty_body;
1492 handler->response_baton = handler;
1494 SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1496 if (handler->sline.code != 201)
1498 apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1500 switch (handler->sline.code)
1503 status = SVN_ERR_RA_DAV_FORBIDDEN;
1506 status = SVN_ERR_FS_NOT_FOUND;
1510 return svn_error_createf(status, NULL,
1511 _("%s of '%s': %d %s (%s://%s)"),
1512 handler->method, handler->path,
1513 handler->sline.code, handler->sline.reason,
1514 ctx->session->session_url.scheme,
1515 ctx->session->session_url.hostinfo);
1518 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1519 SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
1520 ctx->conn, ctx->pool));
1523 /* Build our directory baton. */
1524 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1525 dir->pool = dir_pool;
1527 dir->base_revision = base_revision;
1530 dir->changed_props = apr_hash_make(dir->pool);
1531 dir->removed_props = apr_hash_make(dir->pool);
1533 SVN_ERR(get_version_url(&dir->url, dir->commit->session,
1535 dir->base_revision, ctx->checked_in_url,
1536 dir->pool, dir->pool /* scratch_pool */));
1537 ctx->checked_in_url = dir->url;
1539 /* Checkout our root dir */
1540 SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
1542 proppatch_target = ctx->baseline_url;
1545 /* Unless this is NULL -- which means we don't need to PROPPATCH the
1546 transaction with our revprops -- then, you know, PROPPATCH the
1547 transaction with our revprops. */
1548 if (proppatch_target)
1550 proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
1551 proppatch_ctx->pool = dir_pool;
1552 proppatch_ctx->commit = ctx;
1553 proppatch_ctx->path = proppatch_target;
1554 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
1555 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
1556 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1558 for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
1559 hi = apr_hash_next(hi))
1561 const char *name = svn__apr_hash_index_key(hi);
1562 svn_string_t *value = svn__apr_hash_index_val(hi);
1565 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1567 ns = SVN_DAV_PROP_NS_SVN;
1568 name += sizeof(SVN_PROP_PREFIX) - 1;
1572 ns = SVN_DAV_PROP_NS_CUSTOM;
1575 svn_ra_serf__set_prop(proppatch_ctx->changed_props,
1576 proppatch_ctx->path,
1577 ns, name, value, proppatch_ctx->pool);
1580 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
1585 return SVN_NO_ERROR;
1588 static svn_error_t *
1589 delete_entry(const char *path,
1590 svn_revnum_t revision,
1594 dir_context_t *dir = parent_baton;
1595 delete_context_t *delete_ctx;
1596 svn_ra_serf__handler_t *handler;
1597 const char *delete_target;
1599 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1601 delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
1606 /* Ensure our directory has been checked out */
1607 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1608 delete_target = svn_path_url_add_component2(dir->working_url,
1609 svn_relpath_basename(path,
1614 /* DELETE our entry */
1615 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1616 delete_ctx->relpath = apr_pstrdup(pool, path);
1617 delete_ctx->revision = revision;
1618 delete_ctx->commit = dir->commit;
1620 handler = apr_pcalloc(pool, sizeof(*handler));
1621 handler->handler_pool = pool;
1622 handler->session = dir->commit->session;
1623 handler->conn = dir->commit->conn;
1625 handler->response_handler = svn_ra_serf__expect_empty_body;
1626 handler->response_baton = handler;
1628 handler->header_delegate = setup_delete_headers;
1629 handler->header_delegate_baton = delete_ctx;
1631 handler->method = "DELETE";
1632 handler->path = delete_target;
1634 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1636 /* 204 No Content: item successfully deleted */
1637 if (handler->sline.code != 204)
1639 return svn_error_trace(return_response_err(handler));
1642 svn_hash_sets(dir->commit->deleted_entries,
1643 apr_pstrdup(dir->commit->pool, path), (void *)1);
1645 return SVN_NO_ERROR;
1648 static svn_error_t *
1649 add_directory(const char *path,
1651 const char *copyfrom_path,
1652 svn_revnum_t copyfrom_revision,
1653 apr_pool_t *dir_pool,
1656 dir_context_t *parent = parent_baton;
1658 svn_ra_serf__handler_t *handler;
1659 apr_status_t status;
1660 const char *mkcol_target;
1662 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1664 dir->pool = dir_pool;
1665 dir->parent_dir = parent;
1666 dir->commit = parent->commit;
1668 dir->base_revision = SVN_INVALID_REVNUM;
1669 dir->copy_revision = copyfrom_revision;
1670 dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1671 dir->relpath = apr_pstrdup(dir->pool, path);
1672 dir->name = svn_relpath_basename(dir->relpath, NULL);
1673 dir->changed_props = apr_hash_make(dir->pool);
1674 dir->removed_props = apr_hash_make(dir->pool);
1676 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1678 dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1680 mkcol_target = dir->url;
1684 /* Ensure our parent is checked out. */
1685 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1687 dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
1688 dir->name, dir->pool);
1689 mkcol_target = svn_path_url_add_component2(
1690 parent->working_url,
1691 dir->name, dir->pool);
1694 handler = apr_pcalloc(dir->pool, sizeof(*handler));
1695 handler->handler_pool = dir->pool;
1696 handler->conn = dir->commit->conn;
1697 handler->session = dir->commit->session;
1699 handler->response_handler = svn_ra_serf__expect_empty_body;
1700 handler->response_baton = handler;
1701 if (!dir->copy_path)
1703 handler->method = "MKCOL";
1704 handler->path = mkcol_target;
1706 handler->header_delegate = setup_add_dir_common_headers;
1707 handler->header_delegate_baton = dir;
1712 const char *req_url;
1714 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1717 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1718 _("Unable to parse URL '%s'"),
1722 /* ### conn==NULL for session->conns[0]. same as commit->conn. */
1723 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1724 dir->commit->session,
1726 uri.path, dir->copy_revision,
1727 dir_pool, dir_pool));
1729 handler->method = "COPY";
1730 handler->path = req_url;
1732 handler->header_delegate = setup_copy_dir_headers;
1733 handler->header_delegate_baton = dir;
1736 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1738 switch (handler->sline.code)
1740 case 201: /* Created: item was successfully copied */
1741 case 204: /* No Content: item successfully replaced an existing target */
1745 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1746 _("Access to '%s' forbidden"),
1749 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1750 _("Adding directory failed: %s on %s "
1752 handler->method, handler->path,
1753 handler->sline.code, handler->sline.reason);
1758 return SVN_NO_ERROR;
1761 static svn_error_t *
1762 open_directory(const char *path,
1764 svn_revnum_t base_revision,
1765 apr_pool_t *dir_pool,
1768 dir_context_t *parent = parent_baton;
1771 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1773 dir->pool = dir_pool;
1775 dir->parent_dir = parent;
1776 dir->commit = parent->commit;
1779 dir->base_revision = base_revision;
1780 dir->relpath = apr_pstrdup(dir->pool, path);
1781 dir->name = svn_relpath_basename(dir->relpath, NULL);
1782 dir->changed_props = apr_hash_make(dir->pool);
1783 dir->removed_props = apr_hash_make(dir->pool);
1785 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1787 dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1792 SVN_ERR(get_version_url(&dir->url,
1793 dir->commit->session,
1794 dir->relpath, dir->base_revision,
1795 dir->commit->checked_in_url,
1796 dir->pool, dir->pool /* scratch_pool */));
1800 return SVN_NO_ERROR;
1803 static svn_error_t *
1804 change_dir_prop(void *dir_baton,
1806 const svn_string_t *value,
1809 dir_context_t *dir = dir_baton;
1811 const char *proppatch_target;
1814 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1816 proppatch_target = dir->url;
1820 /* Ensure we have a checked out dir. */
1821 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1823 proppatch_target = dir->working_url;
1826 name = apr_pstrdup(dir->pool, name);
1827 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1829 ns = SVN_DAV_PROP_NS_SVN;
1830 name += sizeof(SVN_PROP_PREFIX) - 1;
1834 ns = SVN_DAV_PROP_NS_CUSTOM;
1839 value = svn_string_dup(value, dir->pool);
1840 svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
1841 ns, name, value, dir->pool);
1845 value = svn_string_create_empty(dir->pool);
1846 svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
1847 ns, name, value, dir->pool);
1850 return SVN_NO_ERROR;
1853 static svn_error_t *
1854 close_directory(void *dir_baton,
1857 dir_context_t *dir = dir_baton;
1859 /* Huh? We're going to be called before the texts are sent. Ugh.
1860 * Therefore, just wave politely at our caller.
1863 /* PROPPATCH our prop change and pass it along. */
1864 if (apr_hash_count(dir->changed_props) ||
1865 apr_hash_count(dir->removed_props))
1867 proppatch_context_t *proppatch_ctx;
1869 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1870 proppatch_ctx->pool = pool;
1871 proppatch_ctx->commit = dir->commit;
1872 proppatch_ctx->relpath = dir->relpath;
1873 proppatch_ctx->changed_props = dir->changed_props;
1874 proppatch_ctx->removed_props = dir->removed_props;
1875 proppatch_ctx->base_revision = dir->base_revision;
1877 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1879 proppatch_ctx->path = dir->url;
1883 proppatch_ctx->path = dir->working_url;
1886 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
1889 return SVN_NO_ERROR;
1892 static svn_error_t *
1893 add_file(const char *path,
1895 const char *copy_path,
1896 svn_revnum_t copy_revision,
1897 apr_pool_t *file_pool,
1900 dir_context_t *dir = parent_baton;
1901 file_context_t *new_file;
1902 const char *deleted_parent = path;
1904 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1905 new_file->pool = file_pool;
1909 new_file->parent_dir = dir;
1910 new_file->commit = dir->commit;
1911 new_file->relpath = apr_pstrdup(new_file->pool, path);
1912 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1913 new_file->added = TRUE;
1914 new_file->base_revision = SVN_INVALID_REVNUM;
1915 new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1916 new_file->copy_revision = copy_revision;
1917 new_file->changed_props = apr_hash_make(new_file->pool);
1918 new_file->removed_props = apr_hash_make(new_file->pool);
1920 /* Ensure that the file doesn't exist by doing a HEAD on the
1921 resource. If we're using HTTP v2, we'll just look into the
1922 transaction root tree for this thing. */
1923 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1925 new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
1926 path, new_file->pool);
1930 /* Ensure our parent directory has been checked out */
1931 SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
1934 svn_path_url_add_component2(dir->working_url,
1935 new_file->name, new_file->pool);
1938 while (deleted_parent && deleted_parent[0] != '\0')
1940 if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
1944 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1947 if (! ((dir->added && !dir->copy_path) ||
1948 (deleted_parent && deleted_parent[0] != '\0')))
1950 svn_ra_serf__handler_t *handler;
1952 handler = apr_pcalloc(new_file->pool, sizeof(*handler));
1953 handler->handler_pool = new_file->pool;
1954 handler->session = new_file->commit->session;
1955 handler->conn = new_file->commit->conn;
1956 handler->method = "HEAD";
1957 handler->path = svn_path_url_add_component2(
1958 dir->commit->session->session_url.path,
1959 path, new_file->pool);
1960 handler->response_handler = svn_ra_serf__expect_empty_body;
1961 handler->response_baton = handler;
1963 SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
1965 if (handler->sline.code != 404)
1967 if (handler->sline.code != 200)
1971 err = svn_ra_serf__error_on_status(handler->sline,
1978 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1979 _("File '%s' already exists"), path);
1983 *file_baton = new_file;
1985 return SVN_NO_ERROR;
1988 static svn_error_t *
1989 open_file(const char *path,
1991 svn_revnum_t base_revision,
1992 apr_pool_t *file_pool,
1995 dir_context_t *parent = parent_baton;
1996 file_context_t *new_file;
1998 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1999 new_file->pool = file_pool;
2001 parent->ref_count++;
2003 new_file->parent_dir = parent;
2004 new_file->commit = parent->commit;
2005 new_file->relpath = apr_pstrdup(new_file->pool, path);
2006 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
2007 new_file->added = FALSE;
2008 new_file->base_revision = base_revision;
2009 new_file->changed_props = apr_hash_make(new_file->pool);
2010 new_file->removed_props = apr_hash_make(new_file->pool);
2012 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
2014 new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url,
2015 path, new_file->pool);
2019 /* CHECKOUT the file into our activity. */
2020 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
2022 new_file->url = new_file->working_url;
2025 *file_baton = new_file;
2027 return SVN_NO_ERROR;
2030 static svn_error_t *
2031 apply_textdelta(void *file_baton,
2032 const char *base_checksum,
2034 svn_txdelta_window_handler_t *handler,
2035 void **handler_baton)
2037 file_context_t *ctx = file_baton;
2039 /* Store the stream in a temporary file; we'll give it to serf when we
2042 * TODO: There should be a way we can stream the request body instead of
2043 * writing to a temporary file (ugh). A special svn stream serf bucket
2044 * that returns EAGAIN until we receive the done call? But, when
2045 * would we run through the serf context? Grr.
2047 * ctx->pool is the same for all files in the commit that send a
2048 * textdelta so this file is explicitly closed in close_file to
2049 * avoid too many simultaneously open files.
2052 SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
2053 svn_io_file_del_on_pool_cleanup,
2056 ctx->stream = svn_stream_create(ctx, pool);
2057 svn_stream_set_write(ctx->stream, svndiff_stream_write);
2059 svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
2060 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
2063 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
2065 return SVN_NO_ERROR;
2068 static svn_error_t *
2069 change_file_prop(void *file_baton,
2071 const svn_string_t *value,
2074 file_context_t *file = file_baton;
2077 name = apr_pstrdup(file->pool, name);
2079 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2081 ns = SVN_DAV_PROP_NS_SVN;
2082 name += sizeof(SVN_PROP_PREFIX) - 1;
2086 ns = SVN_DAV_PROP_NS_CUSTOM;
2091 value = svn_string_dup(value, file->pool);
2092 svn_ra_serf__set_prop(file->changed_props, file->url,
2093 ns, name, value, file->pool);
2097 value = svn_string_create_empty(file->pool);
2099 svn_ra_serf__set_prop(file->removed_props, file->url,
2100 ns, name, value, file->pool);
2103 return SVN_NO_ERROR;
2106 static svn_error_t *
2107 close_file(void *file_baton,
2108 const char *text_checksum,
2109 apr_pool_t *scratch_pool)
2111 file_context_t *ctx = file_baton;
2112 svn_boolean_t put_empty_file = FALSE;
2113 apr_status_t status;
2115 ctx->result_checksum = text_checksum;
2119 svn_ra_serf__handler_t *handler;
2121 const char *req_url;
2123 status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
2126 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2127 _("Unable to parse URL '%s'"),
2131 /* ### conn==NULL for session->conns[0]. same as commit->conn. */
2132 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
2133 ctx->commit->session,
2135 uri.path, ctx->copy_revision,
2136 scratch_pool, scratch_pool));
2138 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2139 handler->handler_pool = scratch_pool;
2140 handler->method = "COPY";
2141 handler->path = req_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;
2148 handler->header_delegate = setup_copy_file_headers;
2149 handler->header_delegate_baton = ctx;
2151 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2153 if (handler->sline.code != 201 && handler->sline.code != 204)
2155 return svn_error_trace(return_response_err(handler));
2159 /* If we got no stream of changes, but this is an added-without-history
2160 * file, make a note that we'll be PUTting a zero-byte file to the server.
2162 if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
2163 put_empty_file = TRUE;
2165 /* If we had a stream of changes, push them to the server... */
2166 if (ctx->stream || put_empty_file)
2168 svn_ra_serf__handler_t *handler;
2170 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2171 handler->handler_pool = scratch_pool;
2172 handler->method = "PUT";
2173 handler->path = ctx->url;
2174 handler->conn = ctx->commit->conn;
2175 handler->session = ctx->commit->session;
2177 handler->response_handler = svn_ra_serf__expect_empty_body;
2178 handler->response_baton = handler;
2182 handler->body_delegate = create_empty_put_body;
2183 handler->body_delegate_baton = ctx;
2184 handler->body_type = "text/plain";
2188 handler->body_delegate = create_put_body;
2189 handler->body_delegate_baton = ctx;
2190 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2193 handler->header_delegate = setup_put_headers;
2194 handler->header_delegate_baton = ctx;
2196 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2198 if (handler->sline.code != 204 && handler->sline.code != 201)
2200 return svn_error_trace(return_response_err(handler));
2205 SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2207 /* If we had any prop changes, push them via PROPPATCH. */
2208 if (apr_hash_count(ctx->changed_props) ||
2209 apr_hash_count(ctx->removed_props))
2211 proppatch_context_t *proppatch;
2213 proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
2214 proppatch->pool = ctx->pool;
2215 proppatch->relpath = ctx->relpath;
2216 proppatch->path = ctx->url;
2217 proppatch->commit = ctx->commit;
2218 proppatch->changed_props = ctx->changed_props;
2219 proppatch->removed_props = ctx->removed_props;
2220 proppatch->base_revision = ctx->base_revision;
2222 SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
2225 return SVN_NO_ERROR;
2228 static svn_error_t *
2229 close_edit(void *edit_baton,
2232 commit_context_t *ctx = edit_baton;
2233 const char *merge_target =
2234 ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2235 const svn_commit_info_t *commit_info;
2238 /* MERGE our activity */
2239 SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
2241 ctx->session->conns[0],
2247 if (response_code != 200)
2249 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2250 _("MERGE request failed: returned %d "
2255 /* Inform the WC that we did a commit. */
2257 SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
2259 /* If we're using activities, DELETE our completed activity. */
2260 if (ctx->activity_url)
2262 svn_ra_serf__handler_t *handler;
2264 handler = apr_pcalloc(pool, sizeof(*handler));
2265 handler->handler_pool = pool;
2266 handler->method = "DELETE";
2267 handler->path = ctx->activity_url;
2268 handler->conn = ctx->conn;
2269 handler->session = ctx->session;
2271 handler->response_handler = svn_ra_serf__expect_empty_body;
2272 handler->response_baton = handler;
2274 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2276 SVN_ERR_ASSERT(handler->sline.code == 204);
2279 return SVN_NO_ERROR;
2282 static svn_error_t *
2283 abort_edit(void *edit_baton,
2286 commit_context_t *ctx = edit_baton;
2287 svn_ra_serf__handler_t *handler;
2289 /* If an activity or transaction wasn't even created, don't bother
2290 trying to delete it. */
2291 if (! (ctx->activity_url || ctx->txn_url))
2292 return SVN_NO_ERROR;
2294 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2295 had a problem. We need to reset it, in order to use it again. */
2296 serf_connection_reset(ctx->session->conns[0]->conn);
2298 /* DELETE our aborted activity */
2299 handler = apr_pcalloc(pool, sizeof(*handler));
2300 handler->handler_pool = pool;
2301 handler->method = "DELETE";
2302 handler->conn = ctx->session->conns[0];
2303 handler->session = ctx->session;
2305 handler->response_handler = svn_ra_serf__expect_empty_body;
2306 handler->response_baton = handler;
2308 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2309 handler->path = ctx->txn_url;
2311 handler->path = ctx->activity_url;
2313 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2316 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2317 404 if the activity wasn't found. */
2318 if (handler->sline.code != 204
2319 && handler->sline.code != 403
2320 && handler->sline.code != 404
2323 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2324 _("DELETE returned unexpected status: %d"),
2325 handler->sline.code);
2328 return SVN_NO_ERROR;
2332 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2333 const svn_delta_editor_t **ret_editor,
2335 apr_hash_t *revprop_table,
2336 svn_commit_callback2_t callback,
2337 void *callback_baton,
2338 apr_hash_t *lock_tokens,
2339 svn_boolean_t keep_locks,
2342 svn_ra_serf__session_t *session = ra_session->priv;
2343 svn_delta_editor_t *editor;
2344 commit_context_t *ctx;
2345 const char *repos_root;
2346 const char *base_relpath;
2347 svn_boolean_t supports_ephemeral_props;
2349 ctx = apr_pcalloc(pool, sizeof(*ctx));
2353 ctx->session = session;
2354 ctx->conn = session->conns[0];
2356 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2358 /* If the server supports ephemeral properties, add some carrying
2359 interesting version information. */
2360 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2361 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2363 if (supports_ephemeral_props)
2365 svn_hash_sets(ctx->revprop_table,
2366 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2367 svn_string_create(SVN_VER_NUMBER, pool));
2368 svn_hash_sets(ctx->revprop_table,
2369 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2370 svn_string_create(session->useragent, pool));
2373 ctx->callback = callback;
2374 ctx->callback_baton = callback_baton;
2376 ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2377 ? lock_tokens : NULL;
2378 ctx->keep_locks = keep_locks;
2380 ctx->deleted_entries = apr_hash_make(ctx->pool);
2382 editor = svn_delta_default_editor(pool);
2383 editor->open_root = open_root;
2384 editor->delete_entry = delete_entry;
2385 editor->add_directory = add_directory;
2386 editor->open_directory = open_directory;
2387 editor->change_dir_prop = change_dir_prop;
2388 editor->close_directory = close_directory;
2389 editor->add_file = add_file;
2390 editor->open_file = open_file;
2391 editor->apply_textdelta = apply_textdelta;
2392 editor->change_file_prop = change_file_prop;
2393 editor->close_file = close_file;
2394 editor->close_edit = close_edit;
2395 editor->abort_edit = abort_edit;
2397 *ret_editor = editor;
2400 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2401 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2404 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2405 *edit_baton, repos_root, base_relpath,
2406 session->shim_callbacks, pool, pool));
2408 return SVN_NO_ERROR;
2412 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2415 const svn_string_t *const *old_value_p,
2416 const svn_string_t *value,
2419 svn_ra_serf__session_t *session = ra_session->priv;
2420 proppatch_context_t *proppatch_ctx;
2421 commit_context_t *commit;
2422 const char *proppatch_target;
2428 svn_boolean_t capable;
2429 SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
2430 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2433 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2434 SVN_ERR_ASSERT(capable);
2437 commit = apr_pcalloc(pool, sizeof(*commit));
2439 commit->pool = pool;
2441 commit->session = session;
2442 commit->conn = session->conns[0];
2444 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2446 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2450 const char *vcc_url;
2452 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
2453 commit->conn, pool));
2455 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2456 commit->conn, vcc_url, rev,
2461 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2463 ns = SVN_DAV_PROP_NS_SVN;
2464 name += sizeof(SVN_PROP_PREFIX) - 1;
2468 ns = SVN_DAV_PROP_NS_CUSTOM;
2471 /* PROPPATCH our log message and pass it along. */
2472 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2473 proppatch_ctx->pool = pool;
2474 proppatch_ctx->commit = commit;
2475 proppatch_ctx->path = proppatch_target;
2476 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
2477 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
2480 proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
2481 proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
2483 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2485 if (old_value_p && *old_value_p)
2487 svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
2488 proppatch_ctx->path,
2489 ns, name, *old_value_p, proppatch_ctx->pool);
2491 else if (old_value_p)
2493 svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
2495 svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
2496 proppatch_ctx->path,
2497 ns, name, dummy_value, proppatch_ctx->pool);
2502 svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
2503 ns, name, value, proppatch_ctx->pool);
2507 value = svn_string_create_empty(proppatch_ctx->pool);
2509 svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
2510 ns, name, value, proppatch_ctx->pool);
2513 err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
2517 (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
2518 _("DAV request failed; it's possible that the repository's "
2519 "pre-revprop-change hook either failed or is non-existent"));
2521 return SVN_NO_ERROR;