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