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