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