1 /*
2 * commit.c : entry point for commit RA functions for ra_serf
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24 #include <apr_uri.h>
25 #include <serf.h>
26
27 #include "svn_hash.h"
28 #include "svn_pools.h"
29 #include "svn_ra.h"
30 #include "svn_dav.h"
31 #include "svn_xml.h"
32 #include "svn_config.h"
33 #include "svn_delta.h"
34 #include "svn_base64.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_props.h"
38
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"
43
44 #include "ra_serf.h"
45 #include "../libsvn_ra/ra_loader.h"
46
47
48 /* Baton passed back with the commit editor. */
49 typedef struct commit_context_t {
50 /* Pool for our commit. */
51 apr_pool_t *pool;
52
53 svn_ra_serf__session_t *session;
54
55 apr_hash_t *revprop_table;
56
57 svn_commit_callback2_t callback;
58 void *callback_baton;
59
60 apr_hash_t *lock_tokens;
61 svn_boolean_t keep_locks;
62 apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */
63
64 /* HTTP v2 stuff */
65 const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */
66 const char *txn_root_url; /* commit anchor txn root URL */
67
68 /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
69 const char *activity_url; /* activity base URL... */
70 const char *baseline_url; /* the working-baseline resource */
71 const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
72 const char *vcc_url; /* vcc url */
73
74 int open_batons; /* Number of open batons */
75 } commit_context_t;
76
77 #define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
78
79 /* Structure associated with a PROPPATCH request. */
80 typedef struct proppatch_context_t {
81 apr_pool_t *pool;
82
83 const char *relpath;
84 const char *path;
85
86 commit_context_t *commit_ctx;
87
88 /* Changed properties. const char * -> svn_prop_t * */
89 apr_hash_t *prop_changes;
90
91 /* Same, for the old value, or NULL. */
92 apr_hash_t *old_props;
93
94 /* In HTTP v2, this is the file/directory version we think we're changing. */
95 svn_revnum_t base_revision;
96
97 } proppatch_context_t;
98
99 typedef struct delete_context_t {
100 const char *relpath;
101
102 svn_revnum_t revision;
103
104 commit_context_t *commit_ctx;
105
106 svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */
107 } delete_context_t;
108
109 /* Represents a directory. */
110 typedef struct dir_context_t {
111 /* Pool for our directory. */
112 apr_pool_t *pool;
113
114 /* The root commit we're in progress for. */
115 commit_context_t *commit_ctx;
116
117 /* URL to operate against (used for CHECKOUT and PROPPATCH before
118 HTTP v2, for PROPPATCH in HTTP v2). */
119 const char *url;
120
121 /* Is this directory being added? (Otherwise, just opened.) */
122 svn_boolean_t added;
123
124 /* Our parent */
125 struct dir_context_t *parent_dir;
126
127 /* The directory name; if "", we're the 'root' */
128 const char *relpath;
129
130 /* The basename of the directory. "" for the 'root' */
131 const char *name;
132
133 /* The base revision of the dir. */
134 svn_revnum_t base_revision;
135
136 const char *copy_path;
137 svn_revnum_t copy_revision;
138
139 /* Changed properties (const char * -> svn_prop_t *) */
140 apr_hash_t *prop_changes;
141
142 /* The checked-out working resource for this directory. May be NULL; if so
143 call checkout_dir() first. */
144 const char *working_url;
145 } dir_context_t;
146
147 /* Represents a file to be committed. */
148 typedef struct file_context_t {
149 /* Pool for our file. */
150 apr_pool_t *pool;
151
152 /* The root commit we're in progress for. */
153 commit_context_t *commit_ctx;
154
155 /* Is this file being added? (Otherwise, just opened.) */
156 svn_boolean_t added;
157
158 dir_context_t *parent_dir;
159
160 const char *relpath;
161 const char *name;
162
163 /* The checked-out working resource for this file. */
164 const char *working_url;
165
166 /* The base revision of the file. */
167 svn_revnum_t base_revision;
168
169 /* Copy path and revision */
170 const char *copy_path;
171 svn_revnum_t copy_revision;
172
173 /* Stream for collecting the svndiff. */
174 svn_stream_t *stream;
175
176 /* Buffer holding the svndiff (can spill to disk). */
177 svn_ra_serf__request_body_t *svndiff;
178
179 /* Did we send the svndiff in apply_textdelta_stream()? */
180 svn_boolean_t svndiff_sent;
181
182 /* Our base checksum as reported by the WC. */
183 const char *base_checksum;
184
185 /* Our resulting checksum as reported by the WC. */
186 const char *result_checksum;
187
188 /* Our resulting checksum as reported by the server. */
189 svn_checksum_t *remote_result_checksum;
190
191 /* Changed properties (const char * -> svn_prop_t *) */
192 apr_hash_t *prop_changes;
193
194 /* URL to PUT the file at. */
195 const char *url;
196
197 } file_context_t;
198
199
200 /* Setup routines and handlers for various requests we'll invoke. */
201
202 /* Implements svn_ra_serf__request_body_delegate_t */
203 static svn_error_t *
create_checkout_body(serf_bucket_t ** bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)204 create_checkout_body(serf_bucket_t **bkt,
205 void *baton,
206 serf_bucket_alloc_t *alloc,
207 apr_pool_t *pool /* request pool */,
208 apr_pool_t *scratch_pool)
209 {
210 const char *activity_url = baton;
211 serf_bucket_t *body_bkt;
212
213 body_bkt = serf_bucket_aggregate_create(alloc);
214
215 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
216 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
217 "xmlns:D", "DAV:",
218 SVN_VA_NULL);
219 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set",
220 SVN_VA_NULL);
221 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href",
222 SVN_VA_NULL);
223
224 SVN_ERR_ASSERT(activity_url != NULL);
225 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
226 activity_url,
227 strlen(activity_url));
228
229 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
230 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
231 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
232 "D:apply-to-version", SVN_VA_NULL);
233 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
234
235 *bkt = body_bkt;
236 return SVN_NO_ERROR;
237 }
238
239
240 /* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
241 given COMMIT_CTX. The resulting working resource will be returned in
242 *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
243 are performed in SCRATCH_POOL.
244
245 ### are these URLs actually repos relpath values? or fspath? or maybe
246 ### the abspath portion of the full URL.
247
248 This function operates synchronously.
249
250 Strictly speaking, we could perform "all" of the CHECKOUT requests
251 when the commit starts, and only block when we need a specific
252 answer. Or, at a minimum, send off these individual requests async
253 and block when we need the answer (eg PUT or PROPPATCH).
254
255 However: the investment to speed this up is not worthwhile, given
256 that CHECKOUT (and the related round trip) is completely obviated
257 in HTTPv2.
258 */
259 static svn_error_t *
checkout_node(const char ** working_url,const commit_context_t * commit_ctx,const char * node_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)260 checkout_node(const char **working_url,
261 const commit_context_t *commit_ctx,
262 const char *node_url,
263 apr_pool_t *result_pool,
264 apr_pool_t *scratch_pool)
265 {
266 svn_ra_serf__handler_t *handler;
267 apr_status_t status;
268 apr_uri_t uri;
269
270 /* HANDLER_POOL is the scratch pool since we don't need to remember
271 anything from the handler. We just want the working resource. */
272 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
273
274 handler->body_delegate = create_checkout_body;
275 handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
276 handler->body_type = "text/xml";
277
278 handler->response_handler = svn_ra_serf__expect_empty_body;
279 handler->response_baton = handler;
280
281 handler->method = "CHECKOUT";
282 handler->path = node_url;
283
284 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
285
286 if (handler->sline.code != 201)
287 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
288
289 if (handler->location == NULL)
290 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
291 _("No Location header received"));
292
293 /* We only want the path portion of the Location header.
294 (code.google.com sometimes returns an 'http:' scheme for an
295 'https:' transaction ... we'll work around that by stripping the
296 scheme, host, and port here and re-adding the correct ones
297 later. */
298 status = apr_uri_parse(scratch_pool, handler->location, &uri);
299 if (status)
300 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
301 _("Error parsing Location header value"));
302
303 *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
304
305 return SVN_NO_ERROR;
306 }
307
308
309 /* This is a wrapper around checkout_node() (which see for
310 documentation) which simply retries the CHECKOUT request when it
311 fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
312 server.
313
314 See https://issues.apache.org/jira/browse/SVN-4127 for
315 details.
316 */
317 static svn_error_t *
retry_checkout_node(const char ** working_url,const commit_context_t * commit_ctx,const char * node_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)318 retry_checkout_node(const char **working_url,
319 const commit_context_t *commit_ctx,
320 const char *node_url,
321 apr_pool_t *result_pool,
322 apr_pool_t *scratch_pool)
323 {
324 svn_error_t *err = SVN_NO_ERROR;
325 int retry_count = 5; /* Magic, arbitrary number. */
326
327 do
328 {
329 svn_error_clear(err);
330
331 err = checkout_node(working_url, commit_ctx, node_url,
332 result_pool, scratch_pool);
333
334 /* There's a small chance of a race condition here if Apache is
335 experiencing heavy commit concurrency or if the network has
336 long latency. It's possible that the value of HEAD changed
337 between the time we fetched the latest baseline and the time
338 we try to CHECKOUT that baseline. If that happens, Apache
339 will throw us a BAD_BASELINE error (deltaV says you can only
340 checkout the latest baseline). We just ignore that specific
341 error and retry a few times, asking for the latest baseline
342 again. */
343 if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
344 return svn_error_trace(err);
345 }
346 while (err && retry_count--);
347
348 return svn_error_trace(err);
349 }
350
351
352 static svn_error_t *
checkout_dir(dir_context_t * dir,apr_pool_t * scratch_pool)353 checkout_dir(dir_context_t *dir,
354 apr_pool_t *scratch_pool)
355 {
356 dir_context_t *c_dir = dir;
357 const char *checkout_url;
358 const char **working;
359
360 if (dir->working_url)
361 {
362 return SVN_NO_ERROR;
363 }
364
365 /* Is this directory or one of our parent dirs newly added?
366 * If so, we're already implicitly checked out. */
367 while (c_dir)
368 {
369 if (c_dir->added)
370 {
371 /* Calculate the working_url by skipping the shared ancestor between
372 * the c_dir_parent->relpath and dir->relpath. This is safe since an
373 * add is guaranteed to have a parent that is checked out. */
374 dir_context_t *c_dir_parent = c_dir->parent_dir;
375 const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath,
376 dir->relpath);
377
378 /* Implicitly checkout this dir now. */
379 SVN_ERR_ASSERT(c_dir_parent->working_url);
380 dir->working_url = svn_path_url_add_component2(
381 c_dir_parent->working_url,
382 relpath, dir->pool);
383 return SVN_NO_ERROR;
384 }
385 c_dir = c_dir->parent_dir;
386 }
387
388 /* We could be called twice for the root: once to checkout the baseline;
389 * once to checkout the directory itself if we need to do so.
390 * Note: CHECKOUT_URL should live longer than HANDLER.
391 */
392 if (!dir->parent_dir && !dir->commit_ctx->baseline_url)
393 {
394 checkout_url = dir->commit_ctx->vcc_url;
395 working = &dir->commit_ctx->baseline_url;
396 }
397 else
398 {
399 checkout_url = dir->url;
400 working = &dir->working_url;
401 }
402
403 /* Checkout our directory into the activity URL now. */
404 return svn_error_trace(retry_checkout_node(working, dir->commit_ctx,
405 checkout_url,
406 dir->pool, scratch_pool));
407 }
408
409
410 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
411 * RELPATH (relative to the root of SESSION).
412 *
413 * Try to find this version url in three ways:
414 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
415 * version url from the working copy properties.
416 * Second, if the version url of the parent directory PARENT_VSN_URL is
417 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
418 * RELPATH.
419 * Else, fetch the version url for the root of SESSION using CONN and
420 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
421 * with RELPATH.
422 *
423 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
424 * temporary allocation.
425 */
426 static svn_error_t *
get_version_url(const char ** checked_in_url,svn_ra_serf__session_t * session,const char * relpath,svn_revnum_t base_revision,const char * parent_vsn_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)427 get_version_url(const char **checked_in_url,
428 svn_ra_serf__session_t *session,
429 const char *relpath,
430 svn_revnum_t base_revision,
431 const char *parent_vsn_url,
432 apr_pool_t *result_pool,
433 apr_pool_t *scratch_pool)
434 {
435 const char *root_checkout;
436
437 if (session->wc_callbacks->get_wc_prop)
438 {
439 const svn_string_t *current_version;
440
441 SVN_ERR(session->wc_callbacks->get_wc_prop(
442 session->wc_callback_baton,
443 relpath,
444 SVN_RA_SERF__WC_CHECKED_IN_URL,
445 ¤t_version, scratch_pool));
446
447 if (current_version)
448 {
449 *checked_in_url =
450 svn_urlpath__canonicalize(current_version->data, result_pool);
451 return SVN_NO_ERROR;
452 }
453 }
454
455 if (parent_vsn_url)
456 {
457 root_checkout = parent_vsn_url;
458 }
459 else
460 {
461 const char *propfind_url;
462
463 if (SVN_IS_VALID_REVNUM(base_revision))
464 {
465 /* mod_dav_svn can't handle the "Label:" header that
466 svn_ra_serf__deliver_props() is going to try to use for
467 this lookup, so we'll do things the hard(er) way, by
468 looking up the version URL from a resource in the
469 baseline collection. */
470 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
471 NULL /* latest_revnum */,
472 session,
473 NULL /* url */, base_revision,
474 scratch_pool, scratch_pool));
475 }
476 else
477 {
478 propfind_url = session->session_url.path;
479 }
480
481 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session,
482 propfind_url, base_revision,
483 "checked-in",
484 scratch_pool, scratch_pool));
485 if (!root_checkout)
486 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
487 _("Path '%s' not present"),
488 session->session_url.path);
489
490 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
491 }
492
493 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
494 result_pool);
495
496 return SVN_NO_ERROR;
497 }
498
499 static svn_error_t *
checkout_file(file_context_t * file,apr_pool_t * scratch_pool)500 checkout_file(file_context_t *file,
501 apr_pool_t *scratch_pool)
502 {
503 dir_context_t *parent_dir = file->parent_dir;
504 const char *checkout_url;
505
506 /* Is one of our parent dirs newly added? If so, we're already
507 * implicitly checked out.
508 */
509 while (parent_dir)
510 {
511 if (parent_dir->added)
512 {
513 /* Implicitly checkout this file now. */
514 SVN_ERR_ASSERT(parent_dir->working_url);
515 file->working_url = svn_path_url_add_component2(
516 parent_dir->working_url,
517 svn_relpath_skip_ancestor(
518 parent_dir->relpath, file->relpath),
519 file->pool);
520 return SVN_NO_ERROR;
521 }
522 parent_dir = parent_dir->parent_dir;
523 }
524
525 SVN_ERR(get_version_url(&checkout_url,
526 file->commit_ctx->session,
527 file->relpath, file->base_revision,
528 NULL, scratch_pool, scratch_pool));
529
530 /* Checkout our file into the activity URL now. */
531 return svn_error_trace(retry_checkout_node(&file->working_url,
532 file->commit_ctx, checkout_url,
533 file->pool, scratch_pool));
534 }
535
536 /* Helper function for proppatch_walker() below. */
537 static svn_error_t *
get_encoding_and_cdata(const char ** encoding_p,const svn_string_t ** encoded_value_p,serf_bucket_alloc_t * alloc,const svn_string_t * value,apr_pool_t * result_pool,apr_pool_t * scratch_pool)538 get_encoding_and_cdata(const char **encoding_p,
539 const svn_string_t **encoded_value_p,
540 serf_bucket_alloc_t *alloc,
541 const svn_string_t *value,
542 apr_pool_t *result_pool,
543 apr_pool_t *scratch_pool)
544 {
545 if (value == NULL)
546 {
547 *encoding_p = NULL;
548 *encoded_value_p = NULL;
549 return SVN_NO_ERROR;
550 }
551
552 /* If a property is XML-safe, XML-encode it. Else, base64-encode
553 it. */
554 if (svn_xml_is_xml_safe(value->data, value->len))
555 {
556 svn_stringbuf_t *xml_esc = NULL;
557 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
558 *encoding_p = NULL;
559 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
560 }
561 else
562 {
563 *encoding_p = "base64";
564 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
565 }
566
567 return SVN_NO_ERROR;
568 }
569
570 /* Helper for create_proppatch_body. Writes per property xml to body */
571 static svn_error_t *
write_prop_xml(const proppatch_context_t * proppatch,serf_bucket_t * body_bkt,serf_bucket_alloc_t * alloc,const svn_prop_t * prop,apr_pool_t * result_pool,apr_pool_t * scratch_pool)572 write_prop_xml(const proppatch_context_t *proppatch,
573 serf_bucket_t *body_bkt,
574 serf_bucket_alloc_t *alloc,
575 const svn_prop_t *prop,
576 apr_pool_t *result_pool,
577 apr_pool_t *scratch_pool)
578 {
579 serf_bucket_t *cdata_bkt;
580 const char *encoding;
581 const svn_string_t *encoded_value;
582 const char *prop_name;
583 const svn_prop_t *old_prop;
584
585 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value,
586 result_pool, scratch_pool));
587 if (encoded_value)
588 {
589 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
590 encoded_value->len,
591 alloc);
592 }
593 else
594 {
595 cdata_bkt = NULL;
596 }
597
598 /* Use the namespace prefix instead of adding the xmlns attribute to support
599 property names containing ':' */
600 if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
601 {
602 prop_name = apr_pstrcat(result_pool,
603 "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1,
604 SVN_VA_NULL);
605 }
606 else
607 {
608 prop_name = apr_pstrcat(result_pool,
609 "C:", prop->name,
610 SVN_VA_NULL);
611 }
612
613 if (cdata_bkt)
614 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
615 "V:encoding", encoding,
616 SVN_VA_NULL);
617 else
618 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
619 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
620 SVN_VA_NULL);
621
622 old_prop = proppatch->old_props
623 ? svn_hash_gets(proppatch->old_props, prop->name)
624 : NULL;
625 if (old_prop)
626 {
627 const char *encoding2;
628 const svn_string_t *encoded_value2;
629 serf_bucket_t *cdata_bkt2;
630
631 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
632 alloc, old_prop->value,
633 result_pool, scratch_pool));
634
635 if (encoded_value2)
636 {
637 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
638 encoded_value2->len,
639 alloc);
640 }
641 else
642 {
643 cdata_bkt2 = NULL;
644 }
645
646 if (cdata_bkt2)
647 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
648 "V:" SVN_DAV__OLD_VALUE,
649 "V:encoding", encoding2,
650 SVN_VA_NULL);
651 else
652 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
653 "V:" SVN_DAV__OLD_VALUE,
654 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
655 SVN_VA_NULL);
656
657 if (cdata_bkt2)
658 serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
659
660 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
661 "V:" SVN_DAV__OLD_VALUE);
662 }
663 if (cdata_bkt)
664 serf_bucket_aggregate_append(body_bkt, cdata_bkt);
665 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
666
667 return SVN_NO_ERROR;
668 }
669
670 /* Possible add the lock-token "If:" precondition header to HEADERS if
671 an examination of COMMIT_CTX and RELPATH indicates that this is the
672 right thing to do.
673
674 Generally speaking, if the client provided a lock token for
675 RELPATH, it's the right thing to do. There is a notable instance
676 where this is not the case, however. If the file at RELPATH was
677 explicitly deleted in this commit already, then mod_dav removed its
678 lock token when it fielded the DELETE request, so we don't want to
679 set the lock precondition again. (See
680 https://issues.apache.org/jira/browse/SVN-3674 for details.)
681 */
682 static svn_error_t *
maybe_set_lock_token_header(serf_bucket_t * headers,commit_context_t * commit_ctx,const char * relpath,apr_pool_t * pool)683 maybe_set_lock_token_header(serf_bucket_t *headers,
684 commit_context_t *commit_ctx,
685 const char *relpath,
686 apr_pool_t *pool)
687 {
688 const char *token;
689
690 if (! commit_ctx->lock_tokens)
691 return SVN_NO_ERROR;
692
693 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
694 {
695 token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
696 if (token)
697 {
698 const char *token_header;
699 const char *token_uri;
700 apr_uri_t uri = commit_ctx->session->session_url;
701
702 /* Supplying the optional URI affects apache response when
703 the lock is broken, see issue 4369. When present any URI
704 must be absolute (RFC 2518 9.4). */
705 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
706 pool);
707 token_uri = apr_uri_unparse(pool, &uri, 0);
708
709 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
710 SVN_VA_NULL);
711 serf_bucket_headers_set(headers, "If", token_header);
712 }
713 }
714
715 return SVN_NO_ERROR;
716 }
717
718 static svn_error_t *
setup_proppatch_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)719 setup_proppatch_headers(serf_bucket_t *headers,
720 void *baton,
721 apr_pool_t *pool /* request pool */,
722 apr_pool_t *scratch_pool)
723 {
724 proppatch_context_t *proppatch = baton;
725
726 if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
727 {
728 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
729 apr_psprintf(pool, "%ld",
730 proppatch->base_revision));
731 }
732
733 if (proppatch->relpath && proppatch->commit_ctx)
734 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx,
735 proppatch->relpath, pool));
736
737 return SVN_NO_ERROR;
738 }
739
740
741 /* Implements svn_ra_serf__request_body_delegate_t */
742 static svn_error_t *
create_proppatch_body(serf_bucket_t ** bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)743 create_proppatch_body(serf_bucket_t **bkt,
744 void *baton,
745 serf_bucket_alloc_t *alloc,
746 apr_pool_t *pool /* request pool */,
747 apr_pool_t *scratch_pool)
748 {
749 proppatch_context_t *ctx = baton;
750 serf_bucket_t *body_bkt;
751 svn_boolean_t opened = FALSE;
752 apr_hash_index_t *hi;
753
754 body_bkt = serf_bucket_aggregate_create(alloc);
755
756 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
757 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
758 "xmlns:D", "DAV:",
759 "xmlns:V", SVN_DAV_PROP_NS_DAV,
760 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
761 "xmlns:S", SVN_DAV_PROP_NS_SVN,
762 SVN_VA_NULL);
763
764 /* First we write property SETs */
765 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
766 hi;
767 hi = apr_hash_next(hi))
768 {
769 svn_prop_t *prop = apr_hash_this_val(hi);
770
771 if (prop->value
772 || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
773 {
774 if (!opened)
775 {
776 opened = TRUE;
777 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set",
778 SVN_VA_NULL);
779 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
780 SVN_VA_NULL);
781 }
782
783 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
784 pool, scratch_pool));
785 }
786 }
787
788 if (opened)
789 {
790 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
791 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
792 }
793
794 /* And then property REMOVEs */
795 opened = FALSE;
796
797 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
798 hi;
799 hi = apr_hash_next(hi))
800 {
801 svn_prop_t *prop = apr_hash_this_val(hi);
802
803 if (!prop->value
804 && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
805 {
806 if (!opened)
807 {
808 opened = TRUE;
809 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove",
810 SVN_VA_NULL);
811 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
812 SVN_VA_NULL);
813 }
814
815 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
816 pool, scratch_pool));
817 }
818 }
819
820 if (opened)
821 {
822 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
823 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
824 }
825
826 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
827
828 *bkt = body_bkt;
829 return SVN_NO_ERROR;
830 }
831
832 static svn_error_t*
proppatch_resource(svn_ra_serf__session_t * session,proppatch_context_t * proppatch,apr_pool_t * pool)833 proppatch_resource(svn_ra_serf__session_t *session,
834 proppatch_context_t *proppatch,
835 apr_pool_t *pool)
836 {
837 svn_ra_serf__handler_t *handler;
838 svn_error_t *err;
839
840 handler = svn_ra_serf__create_handler(session, pool);
841
842 handler->method = "PROPPATCH";
843 handler->path = proppatch->path;
844
845 handler->header_delegate = setup_proppatch_headers;
846 handler->header_delegate_baton = proppatch;
847
848 handler->body_delegate = create_proppatch_body;
849 handler->body_delegate_baton = proppatch;
850 handler->body_type = "text/xml";
851
852 handler->response_handler = svn_ra_serf__handle_multistatus_only;
853 handler->response_baton = handler;
854
855 err = svn_ra_serf__context_run_one(handler, pool);
856
857 if (!err && handler->sline.code != 207)
858 err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
859
860 /* Use specific error code for property handling errors.
861 Use loop to provide the right result with tracing */
862 if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
863 {
864 svn_error_t *e = err;
865
866 while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
867 {
868 e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED;
869 e = e->child;
870 }
871 }
872
873 return svn_error_trace(err);
874 }
875
876 /* Implements svn_ra_serf__request_body_delegate_t */
877 static svn_error_t *
create_empty_put_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)878 create_empty_put_body(serf_bucket_t **body_bkt,
879 void *baton,
880 serf_bucket_alloc_t *alloc,
881 apr_pool_t *pool /* request pool */,
882 apr_pool_t *scratch_pool)
883 {
884 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
885 return SVN_NO_ERROR;
886 }
887
888 static svn_error_t *
setup_put_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)889 setup_put_headers(serf_bucket_t *headers,
890 void *baton,
891 apr_pool_t *pool /* request pool */,
892 apr_pool_t *scratch_pool)
893 {
894 file_context_t *ctx = baton;
895
896 if (SVN_IS_VALID_REVNUM(ctx->base_revision))
897 {
898 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
899 apr_psprintf(pool, "%ld", ctx->base_revision));
900 }
901
902 if (ctx->base_checksum)
903 {
904 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
905 ctx->base_checksum);
906 }
907
908 if (ctx->result_checksum)
909 {
910 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
911 ctx->result_checksum);
912 }
913
914 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx,
915 ctx->relpath, pool));
916
917 return APR_SUCCESS;
918 }
919
920 static svn_error_t *
setup_copy_file_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)921 setup_copy_file_headers(serf_bucket_t *headers,
922 void *baton,
923 apr_pool_t *pool /* request pool */,
924 apr_pool_t *scratch_pool)
925 {
926 file_context_t *file = baton;
927 apr_uri_t uri;
928 const char *absolute_uri;
929
930 /* The Dest URI must be absolute. Bummer. */
931 uri = file->commit_ctx->session->session_url;
932 uri.path = (char*)file->url;
933 absolute_uri = apr_uri_unparse(pool, &uri, 0);
934
935 serf_bucket_headers_set(headers, "Destination", absolute_uri);
936
937 serf_bucket_headers_setn(headers, "Overwrite", "F");
938
939 return SVN_NO_ERROR;
940 }
941
942 static svn_error_t *
setup_if_header_recursive(svn_boolean_t * added,serf_bucket_t * headers,commit_context_t * commit_ctx,const char * rq_relpath,apr_pool_t * pool)943 setup_if_header_recursive(svn_boolean_t *added,
944 serf_bucket_t *headers,
945 commit_context_t *commit_ctx,
946 const char *rq_relpath,
947 apr_pool_t *pool)
948 {
949 svn_stringbuf_t *sb = NULL;
950 apr_hash_index_t *hi;
951 apr_pool_t *iterpool = NULL;
952
953 if (!commit_ctx->lock_tokens)
954 {
955 *added = FALSE;
956 return SVN_NO_ERROR;
957 }
958
959 /* We try to create a directory, so within the Subversion world that
960 would imply that there is nothing here, but mod_dav_svn still sees
961 locks on the old nodes here as in DAV it is perfectly legal to lock
962 something that is not there...
963
964 Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
965 the locks we know of with the request */
966
967 for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
968 hi;
969 hi = apr_hash_next(hi))
970 {
971 const char *relpath = apr_hash_this_key(hi);
972 apr_uri_t uri;
973
974 if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
975 continue;
976 else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
977 {
978 /* When a path is already explicit deleted then its lock
979 will be removed by mod_dav. But mod_dav doesn't remove
980 locks on descendants */
981 continue;
982 }
983
984 if (!iterpool)
985 iterpool = svn_pool_create(pool);
986 else
987 svn_pool_clear(iterpool);
988
989 if (sb == NULL)
990 sb = svn_stringbuf_create("", pool);
991 else
992 svn_stringbuf_appendbyte(sb, ' ');
993
994 uri = commit_ctx->session->session_url;
995 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
996 iterpool);
997
998 svn_stringbuf_appendbyte(sb, '<');
999 svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1000 svn_stringbuf_appendcstr(sb, "> (<");
1001 svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi));
1002 svn_stringbuf_appendcstr(sb, ">)");
1003 }
1004
1005 if (iterpool)
1006 svn_pool_destroy(iterpool);
1007
1008 if (sb)
1009 {
1010 serf_bucket_headers_set(headers, "If", sb->data);
1011 *added = TRUE;
1012 }
1013 else
1014 *added = FALSE;
1015
1016 return SVN_NO_ERROR;
1017 }
1018
1019 static svn_error_t *
setup_add_dir_common_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1020 setup_add_dir_common_headers(serf_bucket_t *headers,
1021 void *baton,
1022 apr_pool_t *pool /* request pool */,
1023 apr_pool_t *scratch_pool)
1024 {
1025 dir_context_t *dir = baton;
1026 svn_boolean_t added;
1027
1028 return svn_error_trace(
1029 setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath,
1030 pool));
1031 }
1032
1033 static svn_error_t *
setup_copy_dir_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1034 setup_copy_dir_headers(serf_bucket_t *headers,
1035 void *baton,
1036 apr_pool_t *pool /* request pool */,
1037 apr_pool_t *scratch_pool)
1038 {
1039 dir_context_t *dir = baton;
1040 apr_uri_t uri;
1041 const char *absolute_uri;
1042
1043 /* The Dest URI must be absolute. Bummer. */
1044 uri = dir->commit_ctx->session->session_url;
1045
1046 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1047 {
1048 uri.path = (char *)dir->url;
1049 }
1050 else
1051 {
1052 uri.path = (char *)svn_path_url_add_component2(
1053 dir->parent_dir->working_url,
1054 dir->name, pool);
1055 }
1056 absolute_uri = apr_uri_unparse(pool, &uri, 0);
1057
1058 serf_bucket_headers_set(headers, "Destination", absolute_uri);
1059
1060 serf_bucket_headers_setn(headers, "Depth", "infinity");
1061 serf_bucket_headers_setn(headers, "Overwrite", "F");
1062
1063 /* Implicitly checkout this dir now. */
1064 dir->working_url = apr_pstrdup(dir->pool, uri.path);
1065
1066 return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool,
1067 scratch_pool));
1068 }
1069
1070 static svn_error_t *
setup_delete_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1071 setup_delete_headers(serf_bucket_t *headers,
1072 void *baton,
1073 apr_pool_t *pool /* request pool */,
1074 apr_pool_t *scratch_pool)
1075 {
1076 delete_context_t *del = baton;
1077 svn_boolean_t added;
1078
1079 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1080 apr_ltoa(pool, del->revision));
1081
1082 if (! del->non_recursive_if)
1083 SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx,
1084 del->relpath, pool));
1085 else
1086 {
1087 SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx,
1088 del->relpath, pool));
1089 added = TRUE;
1090 }
1091
1092 if (added && del->commit_ctx->keep_locks)
1093 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1094 SVN_DAV_OPTION_KEEP_LOCKS);
1095
1096 return SVN_NO_ERROR;
1097 }
1098
1099 /* POST against 'me' resource handlers. */
1100
1101 /* Implements svn_ra_serf__request_body_delegate_t */
1102 static svn_error_t *
create_txn_post_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)1103 create_txn_post_body(serf_bucket_t **body_bkt,
1104 void *baton,
1105 serf_bucket_alloc_t *alloc,
1106 apr_pool_t *pool /* request pool */,
1107 apr_pool_t *scratch_pool)
1108 {
1109 apr_hash_t *revprops = baton;
1110 svn_skel_t *request_skel;
1111 svn_stringbuf_t *skel_str;
1112
1113 request_skel = svn_skel__make_empty_list(pool);
1114 if (revprops)
1115 {
1116 svn_skel_t *proplist_skel;
1117
1118 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1119 svn_skel__prepend(proplist_skel, request_skel);
1120 svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1121 skel_str = svn_skel__unparse(request_skel, pool);
1122 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1123 }
1124 else
1125 {
1126 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1127 }
1128
1129 return SVN_NO_ERROR;
1130 }
1131
1132 /* Implements svn_ra_serf__request_header_delegate_t */
1133 static svn_error_t *
setup_post_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1134 setup_post_headers(serf_bucket_t *headers,
1135 void *baton,
1136 apr_pool_t *pool /* request pool */,
1137 apr_pool_t *scratch_pool)
1138 {
1139 #ifdef SVN_DAV_SEND_VTXN_NAME
1140 /* Enable this to exercise the VTXN-NAME code based on a client
1141 supplied transaction name. */
1142 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1143 svn_uuid_generate(pool));
1144 #endif
1145
1146 return SVN_NO_ERROR;
1147 }
1148
1149
1150 /* Handler baton for POST request. */
1151 typedef struct post_response_ctx_t
1152 {
1153 svn_ra_serf__handler_t *handler;
1154 commit_context_t *commit_ctx;
1155 } post_response_ctx_t;
1156
1157
1158 /* This implements serf_bucket_headers_do_callback_fn_t. */
1159 static int
post_headers_iterator_callback(void * baton,const char * key,const char * val)1160 post_headers_iterator_callback(void *baton,
1161 const char *key,
1162 const char *val)
1163 {
1164 post_response_ctx_t *prc = baton;
1165 commit_context_t *prc_cc = prc->commit_ctx;
1166 svn_ra_serf__session_t *sess = prc_cc->session;
1167
1168 /* If we provided a UUID to the POST request, we should get back
1169 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1170 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
1171 see both. */
1172
1173 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1174 {
1175 /* Build out txn and txn-root URLs using the txn name we're
1176 given, and store the whole lot of it in the commit context. */
1177 prc_cc->txn_url =
1178 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1179 prc_cc->txn_root_url =
1180 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1181 }
1182
1183 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1184 {
1185 /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1186 given, and store the whole lot of it in the commit context. */
1187 prc_cc->txn_url =
1188 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1189 prc_cc->txn_root_url =
1190 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1191 }
1192
1193 return 0;
1194 }
1195
1196
1197 /* A custom serf_response_handler_t which is mostly a wrapper around
1198 svn_ra_serf__expect_empty_body -- it just notices POST response
1199 headers, too.
1200
1201 Implements svn_ra_serf__response_handler_t */
1202 static svn_error_t *
post_response_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * scratch_pool)1203 post_response_handler(serf_request_t *request,
1204 serf_bucket_t *response,
1205 void *baton,
1206 apr_pool_t *scratch_pool)
1207 {
1208 post_response_ctx_t *prc = baton;
1209 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1210
1211 /* Then see which ones we can discover. */
1212 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1213
1214 /* Execute the 'real' response handler to XML-parse the repsonse body. */
1215 return svn_ra_serf__expect_empty_body(request, response,
1216 prc->handler, scratch_pool);
1217 }
1218
1219
1220
1221 /* Commit baton callbacks */
1222
1223 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** root_baton)1224 open_root(void *edit_baton,
1225 svn_revnum_t base_revision,
1226 apr_pool_t *dir_pool,
1227 void **root_baton)
1228 {
1229 commit_context_t *commit_ctx = edit_baton;
1230 svn_ra_serf__handler_t *handler;
1231 proppatch_context_t *proppatch_ctx;
1232 dir_context_t *dir;
1233 apr_hash_index_t *hi;
1234 const char *proppatch_target = NULL;
1235 apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
1236
1237 commit_ctx->open_batons++;
1238
1239 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
1240 {
1241 post_response_ctx_t *prc;
1242 const char *rel_path;
1243 svn_boolean_t post_with_revprops
1244 = (NULL != svn_hash_gets(commit_ctx->session->supported_posts,
1245 "create-txn-with-props"));
1246
1247 /* Create our activity URL now on the server. */
1248 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1249
1250 handler->method = "POST";
1251 handler->body_type = SVN_SKEL_MIME_TYPE;
1252 handler->body_delegate = create_txn_post_body;
1253 handler->body_delegate_baton =
1254 post_with_revprops ? commit_ctx->revprop_table : NULL;
1255 handler->header_delegate = setup_post_headers;
1256 handler->header_delegate_baton = NULL;
1257 handler->path = commit_ctx->session->me_resource;
1258
1259 prc = apr_pcalloc(scratch_pool, sizeof(*prc));
1260 prc->handler = handler;
1261 prc->commit_ctx = commit_ctx;
1262
1263 handler->response_handler = post_response_handler;
1264 handler->response_baton = prc;
1265
1266 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1267
1268 if (handler->sline.code != 201)
1269 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1270
1271 if (! (commit_ctx->txn_root_url && commit_ctx->txn_url))
1272 {
1273 return svn_error_createf(
1274 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1275 _("POST request did not return transaction information"));
1276 }
1277
1278 /* Fixup the txn_root_url to point to the anchor of the commit. */
1279 SVN_ERR(svn_ra_serf__get_relative_path(
1280 &rel_path,
1281 commit_ctx->session->session_url.path,
1282 commit_ctx->session,
1283 scratch_pool));
1284 commit_ctx->txn_root_url = svn_path_url_add_component2(
1285 commit_ctx->txn_root_url,
1286 rel_path, commit_ctx->pool);
1287
1288 /* Build our directory baton. */
1289 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1290 dir->pool = dir_pool;
1291 dir->commit_ctx = commit_ctx;
1292 dir->base_revision = base_revision;
1293 dir->relpath = "";
1294 dir->name = "";
1295 dir->prop_changes = apr_hash_make(dir->pool);
1296 dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url);
1297
1298 /* If we included our revprops in the POST, we need not
1299 PROPPATCH them. */
1300 proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url;
1301 }
1302 else
1303 {
1304 const char *activity_str = commit_ctx->session->activity_collection_url;
1305
1306 if (!activity_str)
1307 SVN_ERR(svn_ra_serf__v1_get_activity_collection(
1308 &activity_str,
1309 commit_ctx->session,
1310 scratch_pool, scratch_pool));
1311
1312 commit_ctx->activity_url = svn_path_url_add_component2(
1313 activity_str,
1314 svn_uuid_generate(scratch_pool),
1315 commit_ctx->pool);
1316
1317 /* Create our activity URL now on the server. */
1318 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1319
1320 handler->method = "MKACTIVITY";
1321 handler->path = commit_ctx->activity_url;
1322
1323 handler->response_handler = svn_ra_serf__expect_empty_body;
1324 handler->response_baton = handler;
1325
1326 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1327
1328 if (handler->sline.code != 201)
1329 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1330
1331 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1332 SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url),
1333 commit_ctx->session, scratch_pool));
1334
1335
1336 /* Build our directory baton. */
1337 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1338 dir->pool = dir_pool;
1339 dir->commit_ctx = commit_ctx;
1340 dir->base_revision = base_revision;
1341 dir->relpath = "";
1342 dir->name = "";
1343 dir->prop_changes = apr_hash_make(dir->pool);
1344
1345 SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session,
1346 dir->relpath,
1347 dir->base_revision, commit_ctx->checked_in_url,
1348 dir->pool, scratch_pool));
1349 commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url);
1350
1351 /* Checkout our root dir */
1352 SVN_ERR(checkout_dir(dir, scratch_pool));
1353
1354 proppatch_target = commit_ctx->baseline_url;
1355 }
1356
1357 /* Unless this is NULL -- which means we don't need to PROPPATCH the
1358 transaction with our revprops -- then, you know, PROPPATCH the
1359 transaction with our revprops. */
1360 if (proppatch_target)
1361 {
1362 proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx));
1363 proppatch_ctx->pool = scratch_pool;
1364 proppatch_ctx->commit_ctx = NULL; /* No lock info */
1365 proppatch_ctx->path = proppatch_target;
1366 proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool);
1367 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1368
1369 for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table);
1370 hi;
1371 hi = apr_hash_next(hi))
1372 {
1373 svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop));
1374
1375 prop->name = apr_hash_this_key(hi);
1376 prop->value = apr_hash_this_val(hi);
1377
1378 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
1379 }
1380
1381 SVN_ERR(proppatch_resource(commit_ctx->session,
1382 proppatch_ctx, scratch_pool));
1383 }
1384
1385 svn_pool_destroy(scratch_pool);
1386
1387 *root_baton = dir;
1388
1389 return SVN_NO_ERROR;
1390 }
1391
1392 /* Implements svn_ra_serf__request_body_delegate_t */
1393 static svn_error_t *
create_delete_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)1394 create_delete_body(serf_bucket_t **body_bkt,
1395 void *baton,
1396 serf_bucket_alloc_t *alloc,
1397 apr_pool_t *pool /* request pool */,
1398 apr_pool_t *scratch_pool)
1399 {
1400 delete_context_t *ctx = baton;
1401 serf_bucket_t *body;
1402
1403 body = serf_bucket_aggregate_create(alloc);
1404
1405 svn_ra_serf__add_xml_header_buckets(body, alloc);
1406
1407 svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens,
1408 ctx->relpath, body, alloc, pool);
1409
1410 *body_bkt = body;
1411 return SVN_NO_ERROR;
1412 }
1413
1414 static svn_error_t *
delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * pool)1415 delete_entry(const char *path,
1416 svn_revnum_t revision,
1417 void *parent_baton,
1418 apr_pool_t *pool)
1419 {
1420 dir_context_t *dir = parent_baton;
1421 delete_context_t *delete_ctx;
1422 svn_ra_serf__handler_t *handler;
1423 const char *delete_target;
1424
1425 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1426 {
1427 delete_target = svn_path_url_add_component2(
1428 dir->commit_ctx->txn_root_url,
1429 path, dir->pool);
1430 }
1431 else
1432 {
1433 /* Ensure our directory has been checked out */
1434 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1435 delete_target = svn_path_url_add_component2(dir->working_url,
1436 svn_relpath_basename(path,
1437 NULL),
1438 pool);
1439 }
1440
1441 /* DELETE our entry */
1442 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1443 delete_ctx->relpath = apr_pstrdup(pool, path);
1444 delete_ctx->revision = revision;
1445 delete_ctx->commit_ctx = dir->commit_ctx;
1446
1447 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1448
1449 handler->response_handler = svn_ra_serf__expect_empty_body;
1450 handler->response_baton = handler;
1451
1452 handler->header_delegate = setup_delete_headers;
1453 handler->header_delegate_baton = delete_ctx;
1454
1455 handler->method = "DELETE";
1456 handler->path = delete_target;
1457 handler->no_fail_on_http_failure_status = TRUE;
1458
1459 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1460
1461 if (handler->sline.code == 400)
1462 {
1463 /* Try again with non-standard body to overcome Apache Httpd
1464 header limit */
1465 delete_ctx->non_recursive_if = TRUE;
1466
1467 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1468
1469 handler->response_handler = svn_ra_serf__expect_empty_body;
1470 handler->response_baton = handler;
1471
1472 handler->header_delegate = setup_delete_headers;
1473 handler->header_delegate_baton = delete_ctx;
1474
1475 handler->method = "DELETE";
1476 handler->path = delete_target;
1477
1478 handler->body_type = "text/xml";
1479 handler->body_delegate = create_delete_body;
1480 handler->body_delegate_baton = delete_ctx;
1481
1482 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1483 }
1484
1485 if (handler->server_error)
1486 return svn_ra_serf__server_error_create(handler, pool);
1487
1488 /* 204 No Content: item successfully deleted */
1489 if (handler->sline.code != 204)
1490 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1491
1492 svn_hash_sets(dir->commit_ctx->deleted_entries,
1493 apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
1494
1495 return SVN_NO_ERROR;
1496 }
1497
1498 static svn_error_t *
add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * dir_pool,void ** child_baton)1499 add_directory(const char *path,
1500 void *parent_baton,
1501 const char *copyfrom_path,
1502 svn_revnum_t copyfrom_revision,
1503 apr_pool_t *dir_pool,
1504 void **child_baton)
1505 {
1506 dir_context_t *parent = parent_baton;
1507 dir_context_t *dir;
1508 svn_ra_serf__handler_t *handler;
1509 apr_status_t status;
1510 const char *mkcol_target;
1511
1512 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1513
1514 dir->pool = dir_pool;
1515 dir->parent_dir = parent;
1516 dir->commit_ctx = parent->commit_ctx;
1517 dir->added = TRUE;
1518 dir->base_revision = SVN_INVALID_REVNUM;
1519 dir->copy_revision = copyfrom_revision;
1520 dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1521 dir->relpath = apr_pstrdup(dir->pool, path);
1522 dir->name = svn_relpath_basename(dir->relpath, NULL);
1523 dir->prop_changes = apr_hash_make(dir->pool);
1524
1525 dir->commit_ctx->open_batons++;
1526
1527 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1528 {
1529 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1530 path, dir->pool);
1531 mkcol_target = dir->url;
1532 }
1533 else
1534 {
1535 /* Ensure our parent is checked out. */
1536 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1537
1538 dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
1539 dir->name, dir->pool);
1540 mkcol_target = svn_path_url_add_component2(
1541 parent->working_url,
1542 dir->name, dir->pool);
1543 }
1544
1545 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
1546
1547 handler->response_handler = svn_ra_serf__expect_empty_body;
1548 handler->response_baton = handler;
1549 if (!dir->copy_path)
1550 {
1551 handler->method = "MKCOL";
1552 handler->path = mkcol_target;
1553
1554 handler->header_delegate = setup_add_dir_common_headers;
1555 handler->header_delegate_baton = dir;
1556 }
1557 else
1558 {
1559 apr_uri_t uri;
1560 const char *req_url;
1561
1562 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1563 if (status)
1564 {
1565 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1566 _("Unable to parse URL '%s'"),
1567 dir->copy_path);
1568 }
1569
1570 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1571 dir->commit_ctx->session,
1572 uri.path, dir->copy_revision,
1573 dir_pool, dir_pool));
1574
1575 handler->method = "COPY";
1576 handler->path = req_url;
1577
1578 handler->header_delegate = setup_copy_dir_headers;
1579 handler->header_delegate_baton = dir;
1580 }
1581 /* We have the same problem as with DELETE here: if there are too many
1582 locks, the request fails. But in this case there is no way to retry
1583 with a non-standard request. #### How to fix? */
1584 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1585
1586 if (handler->sline.code != 201)
1587 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1588
1589 *child_baton = dir;
1590
1591 return SVN_NO_ERROR;
1592 }
1593
1594 static svn_error_t *
open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** child_baton)1595 open_directory(const char *path,
1596 void *parent_baton,
1597 svn_revnum_t base_revision,
1598 apr_pool_t *dir_pool,
1599 void **child_baton)
1600 {
1601 dir_context_t *parent = parent_baton;
1602 dir_context_t *dir;
1603
1604 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1605
1606 dir->pool = dir_pool;
1607
1608 dir->parent_dir = parent;
1609 dir->commit_ctx = parent->commit_ctx;
1610
1611 dir->added = FALSE;
1612 dir->base_revision = base_revision;
1613 dir->relpath = apr_pstrdup(dir->pool, path);
1614 dir->name = svn_relpath_basename(dir->relpath, NULL);
1615 dir->prop_changes = apr_hash_make(dir->pool);
1616
1617 dir->commit_ctx->open_batons++;
1618
1619 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1620 {
1621 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1622 path, dir->pool);
1623 }
1624 else
1625 {
1626 SVN_ERR(get_version_url(&dir->url,
1627 dir->commit_ctx->session,
1628 dir->relpath, dir->base_revision,
1629 dir->commit_ctx->checked_in_url,
1630 dir->pool, dir->pool /* scratch_pool */));
1631 }
1632 *child_baton = dir;
1633
1634 return SVN_NO_ERROR;
1635 }
1636
1637 static svn_error_t *
change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)1638 change_dir_prop(void *dir_baton,
1639 const char *name,
1640 const svn_string_t *value,
1641 apr_pool_t *scratch_pool)
1642 {
1643 dir_context_t *dir = dir_baton;
1644 svn_prop_t *prop;
1645
1646 if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1647 {
1648 /* Ensure we have a checked out dir. */
1649 SVN_ERR(checkout_dir(dir, scratch_pool));
1650 }
1651
1652 prop = apr_palloc(dir->pool, sizeof(*prop));
1653
1654 prop->name = apr_pstrdup(dir->pool, name);
1655 prop->value = svn_string_dup(value, dir->pool);
1656
1657 svn_hash_sets(dir->prop_changes, prop->name, prop);
1658
1659 return SVN_NO_ERROR;
1660 }
1661
1662 static svn_error_t *
close_directory(void * dir_baton,apr_pool_t * pool)1663 close_directory(void *dir_baton,
1664 apr_pool_t *pool)
1665 {
1666 dir_context_t *dir = dir_baton;
1667
1668 /* Huh? We're going to be called before the texts are sent. Ugh.
1669 * Therefore, just wave politely at our caller.
1670 */
1671
1672 /* PROPPATCH our prop change and pass it along. */
1673 if (apr_hash_count(dir->prop_changes))
1674 {
1675 proppatch_context_t *proppatch_ctx;
1676
1677 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1678 proppatch_ctx->pool = pool;
1679 proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
1680 proppatch_ctx->relpath = dir->relpath;
1681 proppatch_ctx->prop_changes = dir->prop_changes;
1682 proppatch_ctx->base_revision = dir->base_revision;
1683
1684 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1685 {
1686 proppatch_ctx->path = dir->url;
1687 }
1688 else
1689 {
1690 proppatch_ctx->path = dir->working_url;
1691 }
1692
1693 SVN_ERR(proppatch_resource(dir->commit_ctx->session,
1694 proppatch_ctx, dir->pool));
1695 }
1696
1697 dir->commit_ctx->open_batons--;
1698
1699 return SVN_NO_ERROR;
1700 }
1701
1702 static svn_error_t *
add_file(const char * path,void * parent_baton,const char * copy_path,svn_revnum_t copy_revision,apr_pool_t * file_pool,void ** file_baton)1703 add_file(const char *path,
1704 void *parent_baton,
1705 const char *copy_path,
1706 svn_revnum_t copy_revision,
1707 apr_pool_t *file_pool,
1708 void **file_baton)
1709 {
1710 dir_context_t *dir = parent_baton;
1711 file_context_t *new_file;
1712 const char *deleted_parent = path;
1713 apr_pool_t *scratch_pool = svn_pool_create(file_pool);
1714
1715 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1716 new_file->pool = file_pool;
1717
1718 new_file->parent_dir = dir;
1719 new_file->commit_ctx = dir->commit_ctx;
1720 new_file->relpath = apr_pstrdup(new_file->pool, path);
1721 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1722 new_file->added = TRUE;
1723 new_file->base_revision = SVN_INVALID_REVNUM;
1724 new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1725 new_file->copy_revision = copy_revision;
1726 new_file->prop_changes = apr_hash_make(new_file->pool);
1727
1728 dir->commit_ctx->open_batons++;
1729
1730 /* Ensure that the file doesn't exist by doing a HEAD on the
1731 resource. If we're using HTTP v2, we'll just look into the
1732 transaction root tree for this thing. */
1733 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1734 {
1735 new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
1736 path, new_file->pool);
1737 }
1738 else
1739 {
1740 /* Ensure our parent directory has been checked out */
1741 SVN_ERR(checkout_dir(dir, scratch_pool));
1742
1743 new_file->url =
1744 svn_path_url_add_component2(dir->working_url,
1745 new_file->name, new_file->pool);
1746 }
1747
1748 while (deleted_parent && deleted_parent[0] != '\0')
1749 {
1750 if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
1751 {
1752 break;
1753 }
1754 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1755 }
1756
1757 if (copy_path)
1758 {
1759 svn_ra_serf__handler_t *handler;
1760 apr_uri_t uri;
1761 const char *req_url;
1762 apr_status_t status;
1763
1764 /* Create the copy directly as cheap 'does exist/out of date'
1765 check. We update the copy (if needed) from close_file() */
1766
1767 status = apr_uri_parse(scratch_pool, copy_path, &uri);
1768 if (status)
1769 return svn_ra_serf__wrap_err(status, NULL);
1770
1771 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1772 dir->commit_ctx->session,
1773 uri.path, copy_revision,
1774 scratch_pool, scratch_pool));
1775
1776 handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1777 scratch_pool);
1778 handler->method = "COPY";
1779 handler->path = req_url;
1780
1781 handler->response_handler = svn_ra_serf__expect_empty_body;
1782 handler->response_baton = handler;
1783
1784 handler->header_delegate = setup_copy_file_headers;
1785 handler->header_delegate_baton = new_file;
1786
1787 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1788
1789 if (handler->sline.code != 201)
1790 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1791 }
1792 else if (! ((dir->added && !dir->copy_path) ||
1793 (deleted_parent && deleted_parent[0] != '\0')))
1794 {
1795 svn_ra_serf__handler_t *handler;
1796 svn_error_t *err;
1797
1798 handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1799 scratch_pool);
1800 handler->method = "HEAD";
1801 handler->path = svn_path_url_add_component2(
1802 dir->commit_ctx->session->session_url.path,
1803 path, scratch_pool);
1804 handler->response_handler = svn_ra_serf__expect_empty_body;
1805 handler->response_baton = handler;
1806 handler->no_dav_headers = TRUE; /* Read only operation outside txn */
1807
1808 err = svn_ra_serf__context_run_one(handler, scratch_pool);
1809
1810 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1811 {
1812 svn_error_clear(err); /* Great. We can create a new file! */
1813 }
1814 else if (err)
1815 return svn_error_trace(err);
1816 else
1817 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1818 _("File '%s' already exists"), path);
1819 }
1820
1821 svn_pool_destroy(scratch_pool);
1822 *file_baton = new_file;
1823
1824 return SVN_NO_ERROR;
1825 }
1826
1827 static svn_error_t *
open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * file_pool,void ** file_baton)1828 open_file(const char *path,
1829 void *parent_baton,
1830 svn_revnum_t base_revision,
1831 apr_pool_t *file_pool,
1832 void **file_baton)
1833 {
1834 dir_context_t *parent = parent_baton;
1835 file_context_t *new_file;
1836
1837 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1838 new_file->pool = file_pool;
1839
1840 new_file->parent_dir = parent;
1841 new_file->commit_ctx = parent->commit_ctx;
1842 new_file->relpath = apr_pstrdup(new_file->pool, path);
1843 new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1844 new_file->added = FALSE;
1845 new_file->base_revision = base_revision;
1846 new_file->prop_changes = apr_hash_make(new_file->pool);
1847
1848 parent->commit_ctx->open_batons++;
1849
1850 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
1851 {
1852 new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1853 path, new_file->pool);
1854 }
1855 else
1856 {
1857 /* CHECKOUT the file into our activity. */
1858 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1859
1860 new_file->url = new_file->working_url;
1861 }
1862
1863 *file_baton = new_file;
1864
1865 return SVN_NO_ERROR;
1866 }
1867
1868 static void
negotiate_put_encoding(int * svndiff_version_p,int * svndiff_compression_level_p,svn_ra_serf__session_t * session)1869 negotiate_put_encoding(int *svndiff_version_p,
1870 int *svndiff_compression_level_p,
1871 svn_ra_serf__session_t *session)
1872 {
1873 int svndiff_version;
1874 int compression_level;
1875
1876 if (session->using_compression == svn_tristate_unknown)
1877 {
1878 /* With http-compression=auto, prefer svndiff2 to svndiff1 with a
1879 * low latency connection (assuming the underlying network has high
1880 * bandwidth), as it is faster and in this case, we don't care about
1881 * worse compression ratio.
1882 *
1883 * Note: For future compatibility, we also handle a theoretically
1884 * possible case where the server has advertised only svndiff2 support.
1885 */
1886 if (session->supports_svndiff2 &&
1887 svn_ra_serf__is_low_latency_connection(session))
1888 svndiff_version = 2;
1889 else if (session->supports_svndiff1)
1890 svndiff_version = 1;
1891 else if (session->supports_svndiff2)
1892 svndiff_version = 2;
1893 else
1894 svndiff_version = 0;
1895 }
1896 else if (session->using_compression == svn_tristate_true)
1897 {
1898 /* Otherwise, prefer svndiff1, as svndiff2 is not a reasonable
1899 * substitute for svndiff1 with default compression level. (It gives
1900 * better speed and compression ratio comparable to svndiff1 with
1901 * compression level 1, but not 5).
1902 *
1903 * Note: For future compatibility, we also handle a theoretically
1904 * possible case where the server has advertised only svndiff2 support.
1905 */
1906 if (session->supports_svndiff1)
1907 svndiff_version = 1;
1908 else if (session->supports_svndiff2)
1909 svndiff_version = 2;
1910 else
1911 svndiff_version = 0;
1912 }
1913 else
1914 {
1915 /* Difference between svndiff formats 0 and 1/2 that format 1/2 allows
1916 * compression. Uncompressed svndiff0 should also be slightly more
1917 * effective if the compression is not required at all.
1918 *
1919 * If the server cannot handle svndiff1/2, or compression is disabled
1920 * with the 'http-compression = no' client configuration option, fall
1921 * back to uncompressed svndiff0 format. As a bonus, users can force
1922 * the usage of the uncompressed format by setting the corresponding
1923 * client configuration option, if they want to.
1924 */
1925 svndiff_version = 0;
1926 }
1927
1928 if (svndiff_version == 0)
1929 compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
1930 else
1931 compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
1932
1933 *svndiff_version_p = svndiff_version;
1934 *svndiff_compression_level_p = compression_level;
1935 }
1936
1937 static svn_error_t *
apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)1938 apply_textdelta(void *file_baton,
1939 const char *base_checksum,
1940 apr_pool_t *pool,
1941 svn_txdelta_window_handler_t *handler,
1942 void **handler_baton)
1943 {
1944 file_context_t *ctx = file_baton;
1945 int svndiff_version;
1946 int compression_level;
1947
1948 /* Construct a holder for the request body; we'll give it to serf when we
1949 * close this file.
1950 *
1951 * Please note that if this callback is used, large request bodies will
1952 * be spilled into temporary files (that requires disk space and prevents
1953 * simultaneous processing by the server and the client). A better approach
1954 * that streams the request body is implemented in apply_textdelta_stream().
1955 * It will be used with most recent servers having the "send result checksum
1956 * in response to a PUT" capability, and only if the editor driver uses the
1957 * new callback.
1958 */
1959 ctx->svndiff =
1960 svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
1961 ctx->pool);
1962 ctx->stream = svn_ra_serf__request_body_get_stream(ctx->svndiff);
1963
1964 negotiate_put_encoding(&svndiff_version, &compression_level,
1965 ctx->commit_ctx->session);
1966 /* Disown the stream; we'll close it explicitly in close_file(). */
1967 svn_txdelta_to_svndiff3(handler, handler_baton,
1968 svn_stream_disown(ctx->stream, pool),
1969 svndiff_version, compression_level, pool);
1970
1971 if (base_checksum)
1972 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
1973
1974 return SVN_NO_ERROR;
1975 }
1976
1977 typedef struct open_txdelta_baton_t
1978 {
1979 svn_ra_serf__session_t *session;
1980 svn_txdelta_stream_open_func_t open_func;
1981 void *open_baton;
1982 svn_error_t *err;
1983 } open_txdelta_baton_t;
1984
1985 static void
txdelta_stream_errfunc(void * baton,svn_error_t * err)1986 txdelta_stream_errfunc(void *baton, svn_error_t *err)
1987 {
1988 open_txdelta_baton_t *b = baton;
1989
1990 /* Remember extended error info from the stream bucket. Note that
1991 * theoretically this errfunc could be called multiple times -- say,
1992 * if the request gets restarted after an error. Compose the errors
1993 * so we don't leak one of them if this happens. */
1994 b->err = svn_error_compose_create(b->err, svn_error_dup(err));
1995 }
1996
1997 /* Implements svn_ra_serf__request_body_delegate_t */
1998 static svn_error_t *
create_body_from_txdelta_stream(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)1999 create_body_from_txdelta_stream(serf_bucket_t **body_bkt,
2000 void *baton,
2001 serf_bucket_alloc_t *alloc,
2002 apr_pool_t *pool /* request pool */,
2003 apr_pool_t *scratch_pool)
2004 {
2005 open_txdelta_baton_t *b = baton;
2006 svn_txdelta_stream_t *txdelta_stream;
2007 svn_stream_t *stream;
2008 int svndiff_version;
2009 int compression_level;
2010
2011 SVN_ERR(b->open_func(&txdelta_stream, b->open_baton, pool, scratch_pool));
2012
2013 negotiate_put_encoding(&svndiff_version, &compression_level, b->session);
2014 stream = svn_txdelta_to_svndiff_stream(txdelta_stream, svndiff_version,
2015 compression_level, pool);
2016 *body_bkt = svn_ra_serf__create_stream_bucket(stream, alloc,
2017 txdelta_stream_errfunc, b);
2018
2019 return SVN_NO_ERROR;
2020 }
2021
2022 /* Handler baton for PUT request. */
2023 typedef struct put_response_ctx_t
2024 {
2025 svn_ra_serf__handler_t *handler;
2026 file_context_t *file_ctx;
2027 } put_response_ctx_t;
2028
2029 /* Implements svn_ra_serf__response_handler_t */
2030 static svn_error_t *
put_response_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * scratch_pool)2031 put_response_handler(serf_request_t *request,
2032 serf_bucket_t *response,
2033 void *baton,
2034 apr_pool_t *scratch_pool)
2035 {
2036 put_response_ctx_t *prc = baton;
2037 serf_bucket_t *hdrs;
2038 const char *val;
2039
2040 hdrs = serf_bucket_response_get_headers(response);
2041 val = serf_bucket_headers_get(hdrs, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
2042 SVN_ERR(svn_checksum_parse_hex(&prc->file_ctx->remote_result_checksum,
2043 svn_checksum_md5, val, prc->file_ctx->pool));
2044
2045 return svn_error_trace(
2046 svn_ra_serf__expect_empty_body(request, response,
2047 prc->handler, scratch_pool));
2048 }
2049
2050 static svn_error_t *
apply_textdelta_stream(const svn_delta_editor_t * editor,void * file_baton,const char * base_checksum,svn_txdelta_stream_open_func_t open_func,void * open_baton,apr_pool_t * scratch_pool)2051 apply_textdelta_stream(const svn_delta_editor_t *editor,
2052 void *file_baton,
2053 const char *base_checksum,
2054 svn_txdelta_stream_open_func_t open_func,
2055 void *open_baton,
2056 apr_pool_t *scratch_pool)
2057 {
2058 file_context_t *ctx = file_baton;
2059 open_txdelta_baton_t open_txdelta_baton = {0};
2060 svn_ra_serf__handler_t *handler;
2061 put_response_ctx_t *prc;
2062 int expected_result;
2063 svn_error_t *err;
2064
2065 /* Remember that we have sent the svndiff. A case when we need to
2066 * perform a zero-byte file PUT (during add_file, close_file editor
2067 * sequences) is handled in close_file().
2068 */
2069 ctx->svndiff_sent = TRUE;
2070 ctx->base_checksum = base_checksum;
2071
2072 handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
2073 scratch_pool);
2074 handler->method = "PUT";
2075 handler->path = ctx->url;
2076
2077 prc = apr_pcalloc(scratch_pool, sizeof(*prc));
2078 prc->handler = handler;
2079 prc->file_ctx = ctx;
2080
2081 handler->response_handler = put_response_handler;
2082 handler->response_baton = prc;
2083
2084 open_txdelta_baton.session = ctx->commit_ctx->session;
2085 open_txdelta_baton.open_func = open_func;
2086 open_txdelta_baton.open_baton = open_baton;
2087 open_txdelta_baton.err = SVN_NO_ERROR;
2088
2089 handler->body_delegate = create_body_from_txdelta_stream;
2090 handler->body_delegate_baton = &open_txdelta_baton;
2091 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2092
2093 handler->header_delegate = setup_put_headers;
2094 handler->header_delegate_baton = ctx;
2095
2096 err = svn_ra_serf__context_run_one(handler, scratch_pool);
2097 /* Do we have an error from the stream bucket? If yes, use it. */
2098 if (open_txdelta_baton.err)
2099 {
2100 svn_error_clear(err);
2101 return svn_error_trace(open_txdelta_baton.err);
2102 }
2103 else if (err)
2104 return svn_error_trace(err);
2105
2106 if (ctx->added && !ctx->copy_path)
2107 expected_result = 201; /* Created */
2108 else
2109 expected_result = 204; /* Updated */
2110
2111 if (handler->sline.code != expected_result)
2112 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2113
2114 return SVN_NO_ERROR;
2115 }
2116
2117 static svn_error_t *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)2118 change_file_prop(void *file_baton,
2119 const char *name,
2120 const svn_string_t *value,
2121 apr_pool_t *pool)
2122 {
2123 file_context_t *file = file_baton;
2124 svn_prop_t *prop;
2125
2126 prop = apr_palloc(file->pool, sizeof(*prop));
2127
2128 prop->name = apr_pstrdup(file->pool, name);
2129 prop->value = svn_string_dup(value, file->pool);
2130
2131 svn_hash_sets(file->prop_changes, prop->name, prop);
2132
2133 return SVN_NO_ERROR;
2134 }
2135
2136 static svn_error_t *
close_file(void * file_baton,const char * text_checksum,apr_pool_t * scratch_pool)2137 close_file(void *file_baton,
2138 const char *text_checksum,
2139 apr_pool_t *scratch_pool)
2140 {
2141 file_context_t *ctx = file_baton;
2142 svn_boolean_t put_empty_file = FALSE;
2143
2144 ctx->result_checksum = text_checksum;
2145
2146 /* If we got no stream of changes, but this is an added-without-history
2147 * file, make a note that we'll be PUTting a zero-byte file to the server.
2148 */
2149 if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
2150 put_empty_file = TRUE;
2151
2152 /* If we have a stream of changes, push them to the server... */
2153 if ((ctx->svndiff || put_empty_file) && !ctx->svndiff_sent)
2154 {
2155 svn_ra_serf__handler_t *handler;
2156 int expected_result;
2157
2158 handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
2159 scratch_pool);
2160
2161 handler->method = "PUT";
2162 handler->path = ctx->url;
2163
2164 handler->response_handler = svn_ra_serf__expect_empty_body;
2165 handler->response_baton = handler;
2166
2167 if (put_empty_file)
2168 {
2169 handler->body_delegate = create_empty_put_body;
2170 handler->body_delegate_baton = ctx;
2171 handler->body_type = "text/plain";
2172 }
2173 else
2174 {
2175 SVN_ERR(svn_stream_close(ctx->stream));
2176
2177 svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
2178 &handler->body_delegate_baton,
2179 ctx->svndiff);
2180 handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2181 }
2182
2183 handler->header_delegate = setup_put_headers;
2184 handler->header_delegate_baton = ctx;
2185
2186 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2187
2188 if (ctx->added && ! ctx->copy_path)
2189 expected_result = 201; /* Created */
2190 else
2191 expected_result = 204; /* Updated */
2192
2193 if (handler->sline.code != expected_result)
2194 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2195 }
2196
2197 /* Don't keep open file handles longer than necessary. */
2198 if (ctx->svndiff)
2199 SVN_ERR(svn_ra_serf__request_body_cleanup(ctx->svndiff, scratch_pool));
2200
2201 /* If we had any prop changes, push them via PROPPATCH. */
2202 if (apr_hash_count(ctx->prop_changes))
2203 {
2204 proppatch_context_t *proppatch;
2205
2206 proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
2207 proppatch->pool = scratch_pool;
2208 proppatch->relpath = ctx->relpath;
2209 proppatch->path = ctx->url;
2210 proppatch->commit_ctx = ctx->commit_ctx;
2211 proppatch->prop_changes = ctx->prop_changes;
2212 proppatch->base_revision = ctx->base_revision;
2213
2214 SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
2215 proppatch, scratch_pool));
2216 }
2217
2218 if (ctx->result_checksum && ctx->remote_result_checksum)
2219 {
2220 svn_checksum_t *result_checksum;
2221
2222 SVN_ERR(svn_checksum_parse_hex(&result_checksum, svn_checksum_md5,
2223 ctx->result_checksum, scratch_pool));
2224
2225 if (!svn_checksum_match(result_checksum, ctx->remote_result_checksum))
2226 return svn_checksum_mismatch_err(result_checksum,
2227 ctx->remote_result_checksum,
2228 scratch_pool,
2229 _("Checksum mismatch for '%s'"),
2230 svn_dirent_local_style(ctx->relpath,
2231 scratch_pool));
2232 }
2233
2234 ctx->commit_ctx->open_batons--;
2235
2236 return SVN_NO_ERROR;
2237 }
2238
2239 static svn_error_t *
close_edit(void * edit_baton,apr_pool_t * pool)2240 close_edit(void *edit_baton,
2241 apr_pool_t *pool)
2242 {
2243 commit_context_t *ctx = edit_baton;
2244 const char *merge_target =
2245 ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2246 const svn_commit_info_t *commit_info;
2247 svn_error_t *err = NULL;
2248
2249 if (ctx->open_batons > 0)
2250 return svn_error_create(
2251 SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
2252 _("Closing editor with directories or files open"));
2253
2254 /* MERGE our activity */
2255 SVN_ERR(svn_ra_serf__run_merge(&commit_info,
2256 ctx->session,
2257 merge_target,
2258 ctx->lock_tokens,
2259 ctx->keep_locks,
2260 pool, pool));
2261
2262 ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
2263
2264 /* Inform the WC that we did a commit. */
2265 if (ctx->callback)
2266 err = ctx->callback(commit_info, ctx->callback_baton, pool);
2267
2268 /* If we're using activities, DELETE our completed activity. */
2269 if (ctx->activity_url)
2270 {
2271 svn_ra_serf__handler_t *handler;
2272
2273 handler = svn_ra_serf__create_handler(ctx->session, pool);
2274
2275 handler->method = "DELETE";
2276 handler->path = ctx->activity_url;
2277
2278 handler->response_handler = svn_ra_serf__expect_empty_body;
2279 handler->response_baton = handler;
2280
2281 ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
2282
2283 SVN_ERR(svn_error_compose_create(
2284 err,
2285 svn_ra_serf__context_run_one(handler, pool)));
2286
2287 if (handler->sline.code != 204)
2288 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2289 }
2290
2291 SVN_ERR(err);
2292
2293 return SVN_NO_ERROR;
2294 }
2295
2296 static svn_error_t *
abort_edit(void * edit_baton,apr_pool_t * pool)2297 abort_edit(void *edit_baton,
2298 apr_pool_t *pool)
2299 {
2300 commit_context_t *ctx = edit_baton;
2301 svn_ra_serf__handler_t *handler;
2302
2303 /* If an activity or transaction wasn't even created, don't bother
2304 trying to delete it. */
2305 if (! (ctx->activity_url || ctx->txn_url))
2306 return SVN_NO_ERROR;
2307
2308 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2309 had a problem. We need to reset it, in order to use it again. */
2310 serf_connection_reset(ctx->session->conns[0]->conn);
2311
2312 /* DELETE our aborted activity */
2313 handler = svn_ra_serf__create_handler(ctx->session, pool);
2314
2315 handler->method = "DELETE";
2316
2317 handler->response_handler = svn_ra_serf__expect_empty_body;
2318 handler->response_baton = handler;
2319 handler->no_fail_on_http_failure_status = TRUE;
2320
2321 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2322 handler->path = ctx->txn_url;
2323 else
2324 handler->path = ctx->activity_url;
2325
2326 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2327
2328 /* 204 if deleted,
2329 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2330 404 if the activity wasn't found. */
2331 if (handler->sline.code != 204
2332 && handler->sline.code != 403
2333 && handler->sline.code != 404)
2334 {
2335 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2336 }
2337
2338 /* Don't delete again if somebody aborts twice */
2339 ctx->activity_url = NULL;
2340 ctx->txn_url = NULL;
2341
2342 return SVN_NO_ERROR;
2343 }
2344
2345 svn_error_t *
svn_ra_serf__get_commit_editor(svn_ra_session_t * ra_session,const svn_delta_editor_t ** ret_editor,void ** edit_baton,apr_hash_t * revprop_table,svn_commit_callback2_t callback,void * callback_baton,apr_hash_t * lock_tokens,svn_boolean_t keep_locks,apr_pool_t * pool)2346 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2347 const svn_delta_editor_t **ret_editor,
2348 void **edit_baton,
2349 apr_hash_t *revprop_table,
2350 svn_commit_callback2_t callback,
2351 void *callback_baton,
2352 apr_hash_t *lock_tokens,
2353 svn_boolean_t keep_locks,
2354 apr_pool_t *pool)
2355 {
2356 svn_ra_serf__session_t *session = ra_session->priv;
2357 svn_delta_editor_t *editor;
2358 commit_context_t *ctx;
2359 const char *repos_root;
2360 const char *base_relpath;
2361 svn_boolean_t supports_ephemeral_props;
2362
2363 ctx = apr_pcalloc(pool, sizeof(*ctx));
2364
2365 ctx->pool = pool;
2366
2367 ctx->session = session;
2368
2369 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2370
2371 /* If the server supports ephemeral properties, add some carrying
2372 interesting version information. */
2373 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2374 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2375 pool));
2376 if (supports_ephemeral_props)
2377 {
2378 svn_hash_sets(ctx->revprop_table,
2379 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2380 svn_string_create(SVN_VER_NUMBER, pool));
2381 svn_hash_sets(ctx->revprop_table,
2382 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2383 svn_string_create(session->useragent, pool));
2384 }
2385
2386 ctx->callback = callback;
2387 ctx->callback_baton = callback_baton;
2388
2389 ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2390 ? lock_tokens : NULL;
2391 ctx->keep_locks = keep_locks;
2392
2393 ctx->deleted_entries = apr_hash_make(ctx->pool);
2394
2395 editor = svn_delta_default_editor(pool);
2396 editor->open_root = open_root;
2397 editor->delete_entry = delete_entry;
2398 editor->add_directory = add_directory;
2399 editor->open_directory = open_directory;
2400 editor->change_dir_prop = change_dir_prop;
2401 editor->close_directory = close_directory;
2402 editor->add_file = add_file;
2403 editor->open_file = open_file;
2404 editor->apply_textdelta = apply_textdelta;
2405 editor->change_file_prop = change_file_prop;
2406 editor->close_file = close_file;
2407 editor->close_edit = close_edit;
2408 editor->abort_edit = abort_edit;
2409 /* Only install the callback that allows streaming PUT request bodies
2410 * if the server has the necessary capability. Otherwise, this will
2411 * fallback to the default implementation using the temporary files.
2412 * See default_editor.c:apply_textdelta_stream(). */
2413 if (session->supports_put_result_checksum)
2414 editor->apply_textdelta_stream = apply_textdelta_stream;
2415
2416 *ret_editor = editor;
2417 *edit_baton = ctx;
2418
2419 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2420 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2421 pool);
2422
2423 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2424 *edit_baton, repos_root, base_relpath,
2425 session->shim_callbacks, pool, pool));
2426
2427 return SVN_NO_ERROR;
2428 }
2429
2430 svn_error_t *
svn_ra_serf__change_rev_prop(svn_ra_session_t * ra_session,svn_revnum_t rev,const char * name,const svn_string_t * const * old_value_p,const svn_string_t * value,apr_pool_t * pool)2431 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2432 svn_revnum_t rev,
2433 const char *name,
2434 const svn_string_t *const *old_value_p,
2435 const svn_string_t *value,
2436 apr_pool_t *pool)
2437 {
2438 svn_ra_serf__session_t *session = ra_session->priv;
2439 proppatch_context_t *proppatch_ctx;
2440 const char *proppatch_target;
2441 const svn_string_t *tmp_old_value;
2442 svn_boolean_t atomic_capable = FALSE;
2443 svn_prop_t *prop;
2444 svn_error_t *err;
2445
2446 if (old_value_p || !value)
2447 SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable,
2448 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2449 pool));
2450
2451 if (old_value_p)
2452 {
2453 /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2454 SVN_ERR_ASSERT(atomic_capable);
2455 }
2456 else if (! value && atomic_capable)
2457 {
2458 svn_string_t *old_value;
2459 /* mod_dav_svn doesn't report a failure when a property delete fails. The
2460 atomic revprop change behavior is a nice workaround, to allow getting
2461 access to the error anyway.
2462
2463 Somehow the mod_dav maintainers think that returning an error from
2464 mod_dav's property delete is an RFC violation.
2465 See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */
2466
2467 SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value,
2468 pool));
2469
2470 if (!old_value)
2471 return SVN_NO_ERROR; /* Nothing to delete */
2472
2473 /* The api expects a double const pointer. Let's make one */
2474 tmp_old_value = old_value;
2475 old_value_p = &tmp_old_value;
2476 }
2477
2478 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2479 {
2480 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2481 }
2482 else
2483 {
2484 const char *vcc_url;
2485
2486 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
2487
2488 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2489 session, vcc_url, rev, "href",
2490 pool, pool));
2491 }
2492
2493 /* PROPPATCH our log message and pass it along. */
2494 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2495 proppatch_ctx->pool = pool;
2496 proppatch_ctx->commit_ctx = NULL; /* No lock headers */
2497 proppatch_ctx->path = proppatch_target;
2498 proppatch_ctx->prop_changes = apr_hash_make(pool);
2499 proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2500
2501 if (old_value_p)
2502 {
2503 prop = apr_palloc(pool, sizeof (*prop));
2504
2505 prop->name = name;
2506 prop->value = *old_value_p;
2507
2508 proppatch_ctx->old_props = apr_hash_make(pool);
2509 svn_hash_sets(proppatch_ctx->old_props, prop->name, prop);
2510 }
2511
2512 prop = apr_palloc(pool, sizeof (*prop));
2513
2514 prop->name = name;
2515 prop->value = value;
2516 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
2517
2518 err = proppatch_resource(session, proppatch_ctx, pool);
2519
2520 /* Use specific error code for old property value mismatch.
2521 Use loop to provide the right result with tracing */
2522 if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2523 {
2524 svn_error_t *e = err;
2525
2526 while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2527 {
2528 e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
2529 e = e->child;
2530 }
2531 }
2532
2533 return svn_error_trace(err);
2534 }
2535