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