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