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