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