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