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