commit.c revision 299742
1139823Simp/*
244165Sjulian * commit.c :  entry point for commit RA functions for ra_serf
344165Sjulian *
444165Sjulian * ====================================================================
544165Sjulian *    Licensed to the Apache Software Foundation (ASF) under one
644165Sjulian *    or more contributor license agreements.  See the NOTICE file
744165Sjulian *    distributed with this work for additional information
844165Sjulian *    regarding copyright ownership.  The ASF licenses this file
944165Sjulian *    to you under the Apache License, Version 2.0 (the
1044165Sjulian *    "License"); you may not use this file except in compliance
1144165Sjulian *    with the License.  You may obtain a copy of the License at
1244165Sjulian *
1344165Sjulian *      http://www.apache.org/licenses/LICENSE-2.0
1444165Sjulian *
1544165Sjulian *    Unless required by applicable law or agreed to in writing,
1644165Sjulian *    software distributed under the License is distributed on an
1744165Sjulian *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1844165Sjulian *    KIND, either express or implied.  See the License for the
1944165Sjulian *    specific language governing permissions and limitations
2044165Sjulian *    under the License.
2144165Sjulian * ====================================================================
2244165Sjulian */
2344165Sjulian
2444165Sjulian#include <apr_uri.h>
2544165Sjulian#include <serf.h>
2644165Sjulian
2744165Sjulian#include "svn_hash.h"
2844165Sjulian#include "svn_pools.h"
2944165Sjulian#include "svn_ra.h"
3044165Sjulian#include "svn_dav.h"
3144165Sjulian#include "svn_xml.h"
3244165Sjulian#include "svn_config.h"
3350477Speter#include "svn_delta.h"
3444165Sjulian#include "svn_base64.h"
3544165Sjulian#include "svn_dirent_uri.h"
3644165Sjulian#include "svn_path.h"
3744165Sjulian#include "svn_props.h"
3844165Sjulian
3944165Sjulian#include "svn_private_config.h"
4044165Sjulian#include "private/svn_dep_compat.h"
4144165Sjulian#include "private/svn_fspath.h"
4244165Sjulian#include "private/svn_skel.h"
4344165Sjulian
4474408Smdodd#include "ra_serf.h"
4574408Smdodd#include "../libsvn_ra/ra_loader.h"
46112285Smdodd
4744165Sjulian
4844165Sjulian/* Baton passed back with the commit editor. */
4944165Sjuliantypedef struct commit_context_t {
50112271Smdodd  /* Pool for our commit. */
51112271Smdodd  apr_pool_t *pool;
5244165Sjulian
53112271Smdodd  svn_ra_serf__session_t *session;
5444165Sjulian
5544165Sjulian  apr_hash_t *revprop_table;
5644165Sjulian
5744165Sjulian  svn_commit_callback2_t callback;
58184710Sbz  void *callback_baton;
59112271Smdodd
6044165Sjulian  apr_hash_t *lock_tokens;
6144165Sjulian  svn_boolean_t keep_locks;
62186119Sqingli  apr_hash_t *deleted_entries;   /* deleted files (for delete+add detection) */
6344165Sjulian
64184710Sbz  /* HTTP v2 stuff */
65112271Smdodd  const char *txn_url;           /* txn URL (!svn/txn/TXN_NAME) */
66112271Smdodd  const char *txn_root_url;      /* commit anchor txn root URL */
67112271Smdodd
6844165Sjulian  /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
6944165Sjulian  const char *activity_url;      /* activity base URL... */
7074408Smdodd  const char *baseline_url;      /* the working-baseline resource */
7144165Sjulian  const char *checked_in_url;    /* checked-in root to base CHECKOUTs from */
7244165Sjulian  const char *vcc_url;           /* vcc url */
7344165Sjulian
7444165Sjulian} commit_context_t;
7574408Smdodd
7674408Smdodd#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
7774408Smdodd
7844165Sjulian/* Structure associated with a PROPPATCH request. */
7974408Smdoddtypedef struct proppatch_context_t {
8074408Smdodd  apr_pool_t *pool;
8174408Smdodd
8274408Smdodd  const char *relpath;
8374408Smdodd  const char *path;
84163606Srwatson
85163606Srwatson  commit_context_t *commit_ctx;
86126907Srwatson
87112277Smdodd  /* Changed properties. const char * -> svn_prop_t * */
88112277Smdodd  apr_hash_t *prop_changes;
89112273Smdodd
90112294Smdodd  /* Same, for the old value, or NULL. */
91112273Smdodd  apr_hash_t *old_props;
92112276Smdodd
9374408Smdodd  /* In HTTP v2, this is the file/directory version we think we're changing. */
94112297Smdodd  svn_revnum_t base_revision;
95112297Smdodd
96112297Smdodd} proppatch_context_t;
9744165Sjulian
98152296Srutypedef struct delete_context_t {
9944165Sjulian  const char *relpath;
100112296Smdodd
101111774Smdodd  svn_revnum_t revision;
10244165Sjulian
103112296Smdodd  commit_context_t *commit_ctx;
104112296Smdodd
10544165Sjulian  svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */
10658313Slile} delete_context_t;
10758313Slile
108112297Smdodd/* Represents a directory. */
109112297Smdoddtypedef struct dir_context_t {
110112297Smdodd  /* Pool for our directory. */
111112297Smdodd  apr_pool_t *pool;
112112297Smdodd
113112297Smdodd  /* The root commit we're in progress for. */
114112297Smdodd  commit_context_t *commit_ctx;
115112297Smdodd
11644165Sjulian  /* URL to operate against (used for CHECKOUT and PROPPATCH before
11758313Slile     HTTP v2, for PROPPATCH in HTTP v2).  */
11844165Sjulian  const char *url;
11944165Sjulian
12044165Sjulian  /* How many pending changes we have left in this directory. */
121152315Sru  unsigned int ref_count;
122152315Sru
123112297Smdodd  /* Is this directory being added?  (Otherwise, just opened.) */
124112272Smdodd  svn_boolean_t added;
125112272Smdodd
126112272Smdodd  /* Our parent */
127152296Sru  struct dir_context_t *parent_dir;
128112297Smdodd
129112297Smdodd  /* The directory name; if "", we're the 'root' */
130112297Smdodd  const char *relpath;
131112297Smdodd
132112297Smdodd  /* The basename of the directory. "" for the 'root' */
13344165Sjulian  const char *name;
13444165Sjulian
13574408Smdodd  /* The base revision of the dir. */
13674408Smdodd  svn_revnum_t base_revision;
13774408Smdodd
13874408Smdodd  const char *copy_path;
13974408Smdodd  svn_revnum_t copy_revision;
14074408Smdodd
14174408Smdodd  /* Changed properties (const char * -> svn_prop_t *) */
14274408Smdodd  apr_hash_t *prop_changes;
143112274Smdodd
14474408Smdodd  /* The checked-out working resource for this directory.  May be NULL; if so
14574408Smdodd     call checkout_dir() first.  */
146112274Smdodd  const char *working_url;
14774408Smdodd} dir_context_t;
148112274Smdodd
149112274Smdodd/* Represents a file to be committed. */
15074408Smdoddtypedef struct file_context_t {
15174408Smdodd  /* Pool for our file. */
15244165Sjulian  apr_pool_t *pool;
15344165Sjulian
15444165Sjulian  /* The root commit we're in progress for. */
155112274Smdodd  commit_context_t *commit_ctx;
156112274Smdodd
157112274Smdodd  /* Is this file being added?  (Otherwise, just opened.) */
15844165Sjulian  svn_boolean_t added;
159112274Smdodd
160112274Smdodd  dir_context_t *parent_dir;
161112274Smdodd
162112274Smdodd  const char *relpath;
16344165Sjulian  const char *name;
16444165Sjulian
16544165Sjulian  /* The checked-out working resource for this file. */
16644165Sjulian  const char *working_url;
16744165Sjulian
16844165Sjulian  /* The base revision of the file. */
16944165Sjulian  svn_revnum_t base_revision;
17044165Sjulian
17184931Sfjoe  /* Copy path and revision */
17244165Sjulian  const char *copy_path;
17374408Smdodd  svn_revnum_t copy_revision;
17474408Smdodd
17574408Smdodd  /* stream */
17674408Smdodd  svn_stream_t *stream;
17774408Smdodd
178120048Smdodd  /* Temporary file containing the svndiff. */
179120048Smdodd  apr_file_t *svndiff;
18074408Smdodd
181120048Smdodd  /* Our base checksum as reported by the WC. */
18274408Smdodd  const char *base_checksum;
183120048Smdodd
184120048Smdodd  /* Our resulting checksum as reported by the WC. */
185152315Sru  const char *result_checksum;
186120048Smdodd
187120048Smdodd  /* Changed properties (const char * -> svn_prop_t *) */
188152315Sru  apr_hash_t *prop_changes;
189120048Smdodd
190120048Smdodd  /* URL to PUT the file at. */
191120048Smdodd  const char *url;
192120048Smdodd
193120048Smdodd} file_context_t;
194120048Smdodd
195120048Smdodd
196120048Smdodd/* Setup routines and handlers for various requests we'll invoke. */
19774408Smdodd
19844165Sjulian/* Implements svn_ra_serf__request_body_delegate_t */
19944165Sjulianstatic svn_error_t *
20044165Sjuliancreate_checkout_body(serf_bucket_t **bkt,
20144165Sjulian                     void *baton,
20244165Sjulian                     serf_bucket_alloc_t *alloc,
20344165Sjulian                     apr_pool_t *pool /* request pool */,
204120047Smdodd                     apr_pool_t *scratch_pool)
20544165Sjulian{
20644165Sjulian  const char *activity_url = baton;
20744165Sjulian  serf_bucket_t *body_bkt;
208152315Sru
20944165Sjulian  body_bkt = serf_bucket_aggregate_create(alloc);
21044165Sjulian
21144165Sjulian  svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
21244165Sjulian  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
21344165Sjulian                                    "xmlns:D", "DAV:",
21444165Sjulian                                    SVN_VA_NULL);
21544165Sjulian  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set",
21644165Sjulian                                    SVN_VA_NULL);
21758313Slile  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href",
21844165Sjulian                                    SVN_VA_NULL);
21944165Sjulian
22044165Sjulian  SVN_ERR_ASSERT(activity_url != NULL);
22144165Sjulian  svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
22244165Sjulian                                     activity_url,
223112274Smdodd                                     strlen(activity_url));
224112274Smdodd
225112274Smdodd  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
22644165Sjulian  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
227112274Smdodd  svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
22844165Sjulian                                     "D:apply-to-version", SVN_VA_NULL);
22944165Sjulian  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
23044165Sjulian
23144165Sjulian  *bkt = body_bkt;
23244165Sjulian  return SVN_NO_ERROR;
23344165Sjulian}
23444165Sjulian
23574408Smdodd
23674408Smdodd/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
23774408Smdodd   given COMMIT_CTX. The resulting working resource will be returned in
23874408Smdodd   *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
23974408Smdodd   are performed in SCRATCH_POOL.
24044165Sjulian
24174408Smdodd   ### are these URLs actually repos relpath values? or fspath? or maybe
24287914Sjlemon   ### the abspath portion of the full URL.
24387914Sjlemon
24474408Smdodd   This function operates synchronously.
24544627Sjulian
24674408Smdodd   Strictly speaking, we could perform "all" of the CHECKOUT requests
247186119Sqingli   when the commit starts, and only block when we need a specific
24844165Sjulian   answer. Or, at a minimum, send off these individual requests async
249112285Smdodd   and block when we need the answer (eg PUT or PROPPATCH).
250172930Srwatson
251112285Smdodd   However: the investment to speed this up is not worthwhile, given
252112285Smdodd   that CHECKOUT (and the related round trip) is completely obviated
253112285Smdodd   in HTTPv2.
254112285Smdodd*/
255112308Smdoddstatic svn_error_t *
256112308Smdoddcheckout_node(const char **working_url,
257148887Srwatson              const commit_context_t *commit_ctx,
258148887Srwatson              const char *node_url,
25944165Sjulian              apr_pool_t *result_pool,
26074408Smdodd              apr_pool_t *scratch_pool)
26174408Smdodd{
262128636Sluigi  svn_ra_serf__handler_t *handler;
263128636Sluigi  apr_status_t status;
26444627Sjulian  apr_uri_t uri;
265186119Sqingli
266102291Sarchie  /* HANDLER_POOL is the scratch pool since we don't need to remember
26796184Skbyanc     anything from the handler. We just want the working resource.  */
26844627Sjulian  handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
26944627Sjulian
27058313Slile  handler->body_delegate = create_checkout_body;
27158313Slile  handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
272152315Sru  handler->body_type = "text/xml";
273112278Smdodd
27444627Sjulian  handler->response_handler = svn_ra_serf__expect_empty_body;
27558313Slile  handler->response_baton = handler;
27644627Sjulian
27796184Skbyanc  handler->method = "CHECKOUT";
27874408Smdodd  handler->path = node_url;
27996184Skbyanc
28096184Skbyanc  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
28144627Sjulian
28244627Sjulian  if (handler->sline.code != 201)
28344627Sjulian    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
28444165Sjulian
28544165Sjulian  if (handler->location == NULL)
28644165Sjulian    return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
287186119Sqingli                            _("No Location header received"));
288128636Sluigi
289128636Sluigi  /* We only want the path portion of the Location header.
29074408Smdodd     (code.google.com sometimes returns an 'http:' scheme for an
29174408Smdodd     'https:' transaction ... we'll work around that by stripping the
292126951Smdodd     scheme, host, and port here and re-adding the correct ones
293126951Smdodd     later.  */
294126951Smdodd  status = apr_uri_parse(scratch_pool, handler->location, &uri);
295126951Smdodd  if (status)
296126951Smdodd    return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
297126951Smdodd                            _("Error parsing Location header value"));
298126951Smdodd
299126951Smdodd  *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
300126951Smdodd
301126951Smdodd  return SVN_NO_ERROR;
302126951Smdodd}
303126951Smdodd
304126951Smdodd
305126951Smdodd/* This is a wrapper around checkout_node() (which see for
306126951Smdodd   documentation) which simply retries the CHECKOUT request when it
307126951Smdodd   fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
308126951Smdodd   server.
309126951Smdodd
310126951Smdodd   See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
311126951Smdodd   details.
312126951Smdodd*/
313126951Smdoddstatic svn_error_t *
314126951Smdoddretry_checkout_node(const char **working_url,
315126951Smdodd                    const commit_context_t *commit_ctx,
316126951Smdodd                    const char *node_url,
317126951Smdodd                    apr_pool_t *result_pool,
318126951Smdodd                    apr_pool_t *scratch_pool)
31974408Smdodd{
32074408Smdodd  svn_error_t *err = SVN_NO_ERROR;
32174408Smdodd  int retry_count = 5; /* Magic, arbitrary number. */
322186217Sqingli
323128636Sluigi  do
324128636Sluigi    {
32574408Smdodd      svn_error_clear(err);
32674408Smdodd
32774408Smdodd      err = checkout_node(working_url, commit_ctx, node_url,
32874408Smdodd                          result_pool, scratch_pool);
32974408Smdodd
33074408Smdodd      /* There's a small chance of a race condition here if Apache is
33174408Smdodd         experiencing heavy commit concurrency or if the network has
33274408Smdodd         long latency.  It's possible that the value of HEAD changed
33374408Smdodd         between the time we fetched the latest baseline and the time
334112278Smdodd         we try to CHECKOUT that baseline.  If that happens, Apache
33574408Smdodd         will throw us a BAD_BASELINE error (deltaV says you can only
336177599Sru         checkout the latest baseline).  We just ignore that specific
33774408Smdodd         error and retry a few times, asking for the latest baseline
33874408Smdodd         again. */
33974408Smdodd      if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
34074408Smdodd        return svn_error_trace(err);
34174408Smdodd    }
34274408Smdodd  while (err && retry_count--);
34374408Smdodd
34474408Smdodd  return svn_error_trace(err);
34574408Smdodd}
34674408Smdodd
34744165Sjulian
34874408Smdoddstatic svn_error_t *
34974408Smdoddcheckout_dir(dir_context_t *dir,
35044627Sjulian             apr_pool_t *scratch_pool)
35144627Sjulian{
35244627Sjulian  dir_context_t *c_dir = dir;
35344627Sjulian  const char *checkout_url;
35444627Sjulian  const char **working;
35544627Sjulian
356108533Sschweikh  if (dir->working_url)
35744627Sjulian    {
35844165Sjulian      return SVN_NO_ERROR;
35944627Sjulian    }
36044627Sjulian
36144627Sjulian  /* Is this directory or one of our parent dirs newly added?
36274408Smdodd   * If so, we're already implicitly checked out. */
363112278Smdodd  while (c_dir)
36474408Smdodd    {
365112280Smdodd      if (c_dir->added)
36644627Sjulian        {
36744165Sjulian          /* Calculate the working_url by skipping the shared ancestor between
36874408Smdodd           * the c_dir_parent->relpath and dir->relpath. This is safe since an
36944165Sjulian           * add is guaranteed to have a parent that is checked out. */
370105598Sbrooks          dir_context_t *c_dir_parent = c_dir->parent_dir;
37144165Sjulian          const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath,
37274408Smdodd                                                          dir->relpath);
37344165Sjulian
37444165Sjulian          /* Implicitly checkout this dir now. */
375112274Smdodd          SVN_ERR_ASSERT(c_dir_parent->working_url);
376112274Smdodd          dir->working_url = svn_path_url_add_component2(
377112274Smdodd                                   c_dir_parent->working_url,
37874408Smdodd                                   relpath, dir->pool);
37974408Smdodd          return SVN_NO_ERROR;
380111790Smdodd        }
38174408Smdodd      c_dir = c_dir->parent_dir;
38274408Smdodd    }
38374408Smdodd
384112281Smdodd  /* We could be called twice for the root: once to checkout the baseline;
38574408Smdodd   * once to checkout the directory itself if we need to do so.
386112268Smdodd   * Note: CHECKOUT_URL should live longer than HANDLER.
387112268Smdodd   */
388112268Smdodd  if (!dir->parent_dir && !dir->commit_ctx->baseline_url)
389112274Smdodd    {
39074408Smdodd      checkout_url = dir->commit_ctx->vcc_url;
39174408Smdodd      working = &dir->commit_ctx->baseline_url;
39244165Sjulian    }
39344165Sjulian  else
39444165Sjulian    {
39544165Sjulian      checkout_url = dir->url;
396111119Simp      working = &dir->working_url;
39744165Sjulian    }
39844165Sjulian
399112274Smdodd  /* Checkout our directory into the activity URL now. */
400112291Smdodd  return svn_error_trace(retry_checkout_node(working, dir->commit_ctx,
40144627Sjulian                                             checkout_url,
40244627Sjulian                                             dir->pool, scratch_pool));
40344627Sjulian}
40444627Sjulian
40544165Sjulian
40644165Sjulian/* Set *CHECKED_IN_URL to the appropriate DAV version url for
40744165Sjulian * RELPATH (relative to the root of SESSION).
40844165Sjulian *
40944165Sjulian * Try to find this version url in three ways:
41044165Sjulian * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
41144165Sjulian * version url from the working copy properties.
41244165Sjulian * Second, if the version url of the parent directory PARENT_VSN_URL is
41344165Sjulian * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
41474408Smdodd * RELPATH.
41544165Sjulian * Else, fetch the version url for the root of SESSION using CONN and
41674408Smdodd * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
41774408Smdodd * with RELPATH.
418112279Smdodd *
41974408Smdodd * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
420112279Smdodd * temporary allocation.
42174408Smdodd */
422112279Smdoddstatic svn_error_t *
423112279Smdoddget_version_url(const char **checked_in_url,
424112279Smdodd                svn_ra_serf__session_t *session,
425112279Smdodd                const char *relpath,
42644165Sjulian                svn_revnum_t base_revision,
42744165Sjulian                const char *parent_vsn_url,
428130549Smlaier                apr_pool_t *result_pool,
429130549Smlaier                apr_pool_t *scratch_pool)
43069152Sjlemon{
431130549Smlaier  const char *root_checkout;
43244165Sjulian
43344165Sjulian  if (session->wc_callbacks->get_wc_prop)
43444165Sjulian    {
43544165Sjulian      const svn_string_t *current_version;
436112296Smdodd
43744165Sjulian      SVN_ERR(session->wc_callbacks->get_wc_prop(
43844165Sjulian                session->wc_callback_baton,
43944165Sjulian                relpath,
44044165Sjulian                SVN_RA_SERF__WC_CHECKED_IN_URL,
44144165Sjulian                &current_version, scratch_pool));
44244165Sjulian
44344165Sjulian      if (current_version)
44444165Sjulian        {
44544165Sjulian          *checked_in_url =
446112299Smdodd            svn_urlpath__canonicalize(current_version->data, result_pool);
44774408Smdodd          return SVN_NO_ERROR;
44874408Smdodd        }
44944165Sjulian    }
450112299Smdodd
451112299Smdodd  if (parent_vsn_url)
452111888Sjlemon    {
453112299Smdodd      root_checkout = parent_vsn_url;
45444165Sjulian    }
455112308Smdodd  else
456112308Smdodd    {
457112308Smdodd      const char *propfind_url;
458112308Smdodd
459112308Smdodd      if (SVN_IS_VALID_REVNUM(base_revision))
460112308Smdodd        {
461112308Smdodd          /* mod_dav_svn can't handle the "Label:" header that
462112308Smdodd             svn_ra_serf__deliver_props() is going to try to use for
463112308Smdodd             this lookup, so we'll do things the hard(er) way, by
464112308Smdodd             looking up the version URL from a resource in the
465112308Smdodd             baseline collection. */
466112308Smdodd          SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
467112308Smdodd                                              NULL /* latest_revnum */,
468112308Smdodd                                              session,
469112308Smdodd                                              NULL /* url */, base_revision,
470112308Smdodd                                              scratch_pool, scratch_pool));
471112308Smdodd        }
472112299Smdodd      else
473112299Smdodd        {
474112299Smdodd          propfind_url = session->session_url.path;
475112299Smdodd        }
476112299Smdodd
477112299Smdodd      SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session,
478112299Smdodd                                          propfind_url, base_revision,
479112299Smdodd                                          "checked-in",
480112286Smdodd                                          scratch_pool, scratch_pool));
481112286Smdodd      if (!root_checkout)
482112286Smdodd        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
483148887Srwatson                                 _("Path '%s' not present"),
484148887Srwatson                                 session->session_url.path);
485112286Smdodd
48644165Sjulian      root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
487112308Smdodd    }
488112308Smdodd
489112308Smdodd  *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
490112308Smdodd                                                result_pool);
491112308Smdodd
492112308Smdodd  return SVN_NO_ERROR;
493112308Smdodd}
494112308Smdodd
495112308Smdoddstatic svn_error_t *
496112308Smdoddcheckout_file(file_context_t *file,
497112308Smdodd              apr_pool_t *scratch_pool)
498112308Smdodd{
499112308Smdodd  dir_context_t *parent_dir = file->parent_dir;
500112285Smdodd  const char *checkout_url;
501172930Srwatson
502112285Smdodd  /* Is one of our parent dirs newly added?  If so, we're already
503112285Smdodd   * implicitly checked out.
504112286Smdodd   */
505112286Smdodd  while (parent_dir)
506112286Smdodd    {
507112299Smdodd      if (parent_dir->added)
50874408Smdodd        {
50958313Slile          /* Implicitly checkout this file now. */
510112280Smdodd          SVN_ERR_ASSERT(parent_dir->working_url);
511112286Smdodd          file->working_url = svn_path_url_add_component2(
512112286Smdodd                                    parent_dir->working_url,
513112286Smdodd                                    svn_relpath_skip_ancestor(
514112286Smdodd                                      parent_dir->relpath, file->relpath),
515112286Smdodd                                    file->pool);
516152315Sru          return SVN_NO_ERROR;
517112286Smdodd        }
518112286Smdodd      parent_dir = parent_dir->parent_dir;
519112286Smdodd    }
520112286Smdodd
521112280Smdodd  SVN_ERR(get_version_url(&checkout_url,
522112280Smdodd                          file->commit_ctx->session,
52344165Sjulian                          file->relpath, file->base_revision,
524126907Srwatson                          NULL, scratch_pool, scratch_pool));
525126907Srwatson
52644165Sjulian  /* Checkout our file into the activity URL now. */
52744165Sjulian  return svn_error_trace(retry_checkout_node(&file->working_url,
52844165Sjulian                                             file->commit_ctx, checkout_url,
52974408Smdodd                                              file->pool, scratch_pool));
530112274Smdodd}
53144165Sjulian
532112299Smdodd/* Helper function for proppatch_walker() below. */
533112299Smdoddstatic svn_error_t *
534112299Smdoddget_encoding_and_cdata(const char **encoding_p,
535112299Smdodd                       const svn_string_t **encoded_value_p,
536112299Smdodd                       serf_bucket_alloc_t *alloc,
537112299Smdodd                       const svn_string_t *value,
538112299Smdodd                       apr_pool_t *result_pool,
539112299Smdodd                       apr_pool_t *scratch_pool)
540112299Smdodd{
541112299Smdodd  if (value == NULL)
542112299Smdodd    {
543112299Smdodd      *encoding_p = NULL;
544112299Smdodd      *encoded_value_p = NULL;
54574408Smdodd      return SVN_NO_ERROR;
54644165Sjulian    }
54774408Smdodd
54874408Smdodd  /* If a property is XML-safe, XML-encode it.  Else, base64-encode
54974408Smdodd     it. */
55074408Smdodd  if (svn_xml_is_xml_safe(value->data, value->len))
551112289Smdodd    {
552112289Smdodd      svn_stringbuf_t *xml_esc = NULL;
55374408Smdodd      svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
554112289Smdodd      *encoding_p = NULL;
55574408Smdodd      *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
55674408Smdodd    }
55774408Smdodd  else
558111888Sjlemon    {
55974408Smdodd      *encoding_p = "base64";
56074408Smdodd      *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
56174408Smdodd    }
56274408Smdodd
56374408Smdodd  return SVN_NO_ERROR;
564112289Smdodd}
565112289Smdodd
56674408Smdodd/* Helper for create_proppatch_body. Writes per property xml to body */
567112289Smdoddstatic svn_error_t *
56874408Smdoddwrite_prop_xml(const proppatch_context_t *proppatch,
569112268Smdodd               serf_bucket_t *body_bkt,
570112268Smdodd               serf_bucket_alloc_t *alloc,
571112294Smdodd               const svn_prop_t *prop,
572112294Smdodd               apr_pool_t *result_pool,
57374408Smdodd               apr_pool_t *scratch_pool)
574112294Smdodd{
57574408Smdodd  serf_bucket_t *cdata_bkt;
576112268Smdodd  const char *encoding;
577111790Smdodd  const svn_string_t *encoded_value;
57874408Smdodd  const char *prop_name;
57944165Sjulian  const svn_prop_t *old_prop;
58074408Smdodd
58174408Smdodd  SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value,
582154518Sandre                                 result_pool, scratch_pool));
58374408Smdodd  if (encoded_value)
584111888Sjlemon    {
58574408Smdodd      cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
58674408Smdodd                                                encoded_value->len,
58774408Smdodd                                                alloc);
58878295Sjlemon    }
58978295Sjlemon  else
590111888Sjlemon    {
59174408Smdodd      cdata_bkt = NULL;
59274408Smdodd    }
59374408Smdodd
59474408Smdodd  /* Use the namespace prefix instead of adding the xmlns attribute to support
59574408Smdodd     property names containing ':' */
596111888Sjlemon  if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
59774408Smdodd    {
59874408Smdodd      prop_name = apr_pstrcat(result_pool,
59974408Smdodd                              "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1,
60074408Smdodd                              SVN_VA_NULL);
60174408Smdodd    }
602111888Sjlemon  else
60374408Smdodd    {
60474408Smdodd      prop_name = apr_pstrcat(result_pool,
60574408Smdodd                              "C:", prop->name,
60674408Smdodd                              SVN_VA_NULL);
607112289Smdodd    }
608112289Smdodd
60974408Smdodd  if (cdata_bkt)
61044165Sjulian    svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
61174408Smdodd                                      "V:encoding", encoding,
612112296Smdodd                                      SVN_VA_NULL);
61374408Smdodd  else
61474408Smdodd    svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
61574408Smdodd                                      "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
616112289Smdodd                                      SVN_VA_NULL);
61774408Smdodd
61874408Smdodd  old_prop = proppatch->old_props
61974408Smdodd                          ? svn_hash_gets(proppatch->old_props, prop->name)
62074408Smdodd                          : NULL;
62174408Smdodd  if (old_prop)
62274408Smdodd    {
62374408Smdodd      const char *encoding2;
62474408Smdodd      const svn_string_t *encoded_value2;
62574408Smdodd      serf_bucket_t *cdata_bkt2;
62674408Smdodd
62774408Smdodd      SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
62874408Smdodd                                     alloc, old_prop->value,
62974408Smdodd                                     result_pool, scratch_pool));
63074408Smdodd
63174408Smdodd      if (encoded_value2)
632112296Smdodd        {
63374408Smdodd          cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
63474408Smdodd                                                     encoded_value2->len,
635112296Smdodd                                                     alloc);
63644165Sjulian        }
637112296Smdodd      else
638112296Smdodd        {
63974408Smdodd          cdata_bkt2 = NULL;
64074408Smdodd        }
641112289Smdodd
64274408Smdodd      if (cdata_bkt2)
64374408Smdodd        svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
64474408Smdodd                                          "V:" SVN_DAV__OLD_VALUE,
64574408Smdodd                                          "V:encoding", encoding2,
646152315Sru                                          SVN_VA_NULL);
647112280Smdodd      else
64874408Smdodd        svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
64974408Smdodd                                          "V:" SVN_DAV__OLD_VALUE,
65074408Smdodd                                          "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
65174408Smdodd                                          SVN_VA_NULL);
65274408Smdodd
65374408Smdodd      if (cdata_bkt2)
65474408Smdodd        serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
65574408Smdodd
65674408Smdodd      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
65774408Smdodd                                         "V:" SVN_DAV__OLD_VALUE);
65874408Smdodd    }
65974408Smdodd  if (cdata_bkt)
66074408Smdodd    serf_bucket_aggregate_append(body_bkt, cdata_bkt);
66174408Smdodd  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
66274408Smdodd
66374408Smdodd  return SVN_NO_ERROR;
66474408Smdodd}
665112289Smdodd
666112289Smdodd/* Possible add the lock-token "If:" precondition header to HEADERS if
667112294Smdodd   an examination of COMMIT_CTX and RELPATH indicates that this is the
66874408Smdodd   right thing to do.
66974408Smdodd
670112296Smdodd   Generally speaking, if the client provided a lock token for
67144165Sjulian   RELPATH, it's the right thing to do.  There is a notable instance
67274408Smdodd   where this is not the case, however.  If the file at RELPATH was
67374408Smdodd   explicitly deleted in this commit already, then mod_dav removed its
674112289Smdodd   lock token when it fielded the DELETE request, so we don't want to
675112294Smdodd   set the lock precondition again.  (See
67644165Sjulian   http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
677112274Smdodd*/
678111888Sjlemonstatic svn_error_t *
679112274Smdoddmaybe_set_lock_token_header(serf_bucket_t *headers,
680112289Smdodd                            commit_context_t *commit_ctx,
681112289Smdodd                            const char *relpath,
682112289Smdodd                            apr_pool_t *pool)
683112289Smdodd{
684112289Smdodd  const char *token;
685112289Smdodd
68644165Sjulian  if (! (*relpath && commit_ctx->lock_tokens))
687112269Smdodd    return SVN_NO_ERROR;
688112273Smdodd
689112273Smdodd  if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
690112273Smdodd    {
691112273Smdodd      token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
692112273Smdodd      if (token)
693112273Smdodd        {
694112273Smdodd          const char *token_header;
695184710Sbz          const char *token_uri;
696112273Smdodd          apr_uri_t uri = commit_ctx->session->session_url;
697184710Sbz
698112273Smdodd          /* Supplying the optional URI affects apache response when
699112273Smdodd             the lock is broken, see issue 4369.  When present any URI
700112273Smdodd             must be absolute (RFC 2518 9.4). */
701112273Smdodd          uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
702112273Smdodd                                                         pool);
703112273Smdodd          token_uri = apr_uri_unparse(pool, &uri, 0);
704112273Smdodd
705112273Smdodd          token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
706112273Smdodd                                     SVN_VA_NULL);
707112273Smdodd          serf_bucket_headers_set(headers, "If", token_header);
708112273Smdodd        }
709112273Smdodd    }
710112273Smdodd
711112273Smdodd  return SVN_NO_ERROR;
712112273Smdodd}
713112273Smdodd
714112273Smdoddstatic svn_error_t *
715112273Smdoddsetup_proppatch_headers(serf_bucket_t *headers,
716112273Smdodd                        void *baton,
717112273Smdodd                        apr_pool_t *pool /* request pool */,
718112273Smdodd                        apr_pool_t *scratch_pool)
719112273Smdodd{
720112273Smdodd  proppatch_context_t *proppatch = baton;
721112273Smdodd
722184205Sdes  if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
723148641Srwatson    {
724148641Srwatson      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
725148641Srwatson                              apr_psprintf(pool, "%ld",
726112273Smdodd                                           proppatch->base_revision));
727112273Smdodd    }
728112273Smdodd
729112273Smdodd  if (proppatch->relpath && proppatch->commit_ctx)
730112273Smdodd    SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx,
731112273Smdodd                                        proppatch->relpath, pool));
732112273Smdodd
733112273Smdodd  return SVN_NO_ERROR;
734112273Smdodd}
735112273Smdodd
736112273Smdodd
737112273Smdodd/* Implements svn_ra_serf__request_body_delegate_t */
738112273Smdoddstatic svn_error_t *
739112273Smdoddcreate_proppatch_body(serf_bucket_t **bkt,
740112273Smdodd                      void *baton,
741112273Smdodd                      serf_bucket_alloc_t *alloc,
742112273Smdodd                      apr_pool_t *pool /* request pool */,
743112273Smdodd                      apr_pool_t *scratch_pool)
744112273Smdodd{
745112273Smdodd  proppatch_context_t *ctx = baton;
746112273Smdodd  serf_bucket_t *body_bkt;
747112273Smdodd  svn_boolean_t opened = FALSE;
748112273Smdodd  apr_hash_index_t *hi;
749112273Smdodd
750112273Smdodd  body_bkt = serf_bucket_aggregate_create(alloc);
751112273Smdodd
752184205Sdes  svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
753148641Srwatson  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
754148641Srwatson                                    "xmlns:D", "DAV:",
755148641Srwatson                                    "xmlns:V", SVN_DAV_PROP_NS_DAV,
756112273Smdodd                                    "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
757112273Smdodd                                    "xmlns:S", SVN_DAV_PROP_NS_SVN,
758112273Smdodd                                    SVN_VA_NULL);
759112273Smdodd
760112273Smdodd  /* First we write property SETs */
761112273Smdodd  for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
762112273Smdodd       hi;
763112273Smdodd       hi = apr_hash_next(hi))
764112273Smdodd    {
765112273Smdodd      svn_prop_t *prop = apr_hash_this_val(hi);
766112273Smdodd
767112273Smdodd      if (prop->value
768112273Smdodd          || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
769112273Smdodd        {
770112273Smdodd          if (!opened)
771112273Smdodd            {
772112273Smdodd              opened = TRUE;
773112273Smdodd              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set",
774112273Smdodd                                                SVN_VA_NULL);
775112273Smdodd              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
776112273Smdodd                                                SVN_VA_NULL);
777112273Smdodd            }
778147256Sbrooks
779147256Sbrooks          SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
780147256Sbrooks                                 pool, scratch_pool));
781147256Sbrooks        }
782147256Sbrooks    }
783147256Sbrooks
784147256Sbrooks  if (opened)
785147256Sbrooks    {
786147256Sbrooks      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
787147256Sbrooks      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
788147256Sbrooks    }
789147256Sbrooks
790147256Sbrooks  /* And then property REMOVEs */
791147256Sbrooks  opened = FALSE;
792147256Sbrooks
793147256Sbrooks  for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
794147256Sbrooks       hi;
795147256Sbrooks       hi = apr_hash_next(hi))
796147256Sbrooks    {
797147256Sbrooks      svn_prop_t *prop = apr_hash_this_val(hi);
798147256Sbrooks
799147256Sbrooks      if (!prop->value
800147256Sbrooks          && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
801147256Sbrooks        {
802147256Sbrooks          if (!opened)
803147256Sbrooks            {
804147256Sbrooks              opened = TRUE;
805147256Sbrooks              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove",
806147256Sbrooks                                                SVN_VA_NULL);
807147256Sbrooks              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
808147256Sbrooks                                                SVN_VA_NULL);
809147256Sbrooks            }
810147256Sbrooks
811147256Sbrooks          SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
812147256Sbrooks                                 pool, scratch_pool));
813147256Sbrooks        }
814147256Sbrooks    }
815147256Sbrooks
816147256Sbrooks  if (opened)
817112269Smdodd    {
818112269Smdodd      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
819147256Sbrooks      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
820112269Smdodd    }
821112269Smdodd
822112269Smdodd  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
823112269Smdodd
824112269Smdodd  *bkt = body_bkt;
825  return SVN_NO_ERROR;
826}
827
828static svn_error_t*
829proppatch_resource(svn_ra_serf__session_t *session,
830                   proppatch_context_t *proppatch,
831                   apr_pool_t *pool)
832{
833  svn_ra_serf__handler_t *handler;
834  svn_error_t *err;
835
836  handler = svn_ra_serf__create_handler(session, pool);
837
838  handler->method = "PROPPATCH";
839  handler->path = proppatch->path;
840
841  handler->header_delegate = setup_proppatch_headers;
842  handler->header_delegate_baton = proppatch;
843
844  handler->body_delegate = create_proppatch_body;
845  handler->body_delegate_baton = proppatch;
846  handler->body_type = "text/xml";
847
848  handler->response_handler = svn_ra_serf__handle_multistatus_only;
849  handler->response_baton = handler;
850
851  err = svn_ra_serf__context_run_one(handler, pool);
852
853  if (!err && handler->sline.code != 207)
854    err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
855
856  /* Use specific error code for property handling errors.
857     Use loop to provide the right result with tracing */
858  if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
859    {
860      svn_error_t *e = err;
861
862      while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
863        {
864          e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED;
865          e = e->child;
866        }
867    }
868
869  return svn_error_trace(err);
870}
871
872/* Implements svn_ra_serf__request_body_delegate_t */
873static svn_error_t *
874create_put_body(serf_bucket_t **body_bkt,
875                void *baton,
876                serf_bucket_alloc_t *alloc,
877                apr_pool_t *pool /* request pool */,
878                apr_pool_t *scratch_pool)
879{
880  file_context_t *ctx = baton;
881  apr_off_t offset;
882
883  /* We need to flush the file, make it unbuffered (so that it can be
884   * zero-copied via mmap), and reset the position before attempting to
885   * deliver the file.
886   *
887   * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
888   * and zero-copy the PUT body.  However, on older APR versions, we can't
889   * check the buffer status; but serf will fall through and create a file
890   * bucket for us on the buffered svndiff handle.
891   */
892  SVN_ERR(svn_io_file_flush(ctx->svndiff, pool));
893  apr_file_buffer_set(ctx->svndiff, NULL, 0);
894  offset = 0;
895  SVN_ERR(svn_io_file_seek(ctx->svndiff, APR_SET, &offset, pool));
896
897  *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
898  return SVN_NO_ERROR;
899}
900
901/* Implements svn_ra_serf__request_body_delegate_t */
902static svn_error_t *
903create_empty_put_body(serf_bucket_t **body_bkt,
904                      void *baton,
905                      serf_bucket_alloc_t *alloc,
906                      apr_pool_t *pool /* request pool */,
907                      apr_pool_t *scratch_pool)
908{
909  *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
910  return SVN_NO_ERROR;
911}
912
913static svn_error_t *
914setup_put_headers(serf_bucket_t *headers,
915                  void *baton,
916                  apr_pool_t *pool /* request pool */,
917                  apr_pool_t *scratch_pool)
918{
919  file_context_t *ctx = baton;
920
921  if (SVN_IS_VALID_REVNUM(ctx->base_revision))
922    {
923      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
924                              apr_psprintf(pool, "%ld", ctx->base_revision));
925    }
926
927  if (ctx->base_checksum)
928    {
929      serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
930                              ctx->base_checksum);
931    }
932
933  if (ctx->result_checksum)
934    {
935      serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
936                              ctx->result_checksum);
937    }
938
939  SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx,
940                                      ctx->relpath, pool));
941
942  return APR_SUCCESS;
943}
944
945static svn_error_t *
946setup_copy_file_headers(serf_bucket_t *headers,
947                        void *baton,
948                        apr_pool_t *pool /* request pool */,
949                        apr_pool_t *scratch_pool)
950{
951  file_context_t *file = baton;
952  apr_uri_t uri;
953  const char *absolute_uri;
954
955  /* The Dest URI must be absolute.  Bummer. */
956  uri = file->commit_ctx->session->session_url;
957  uri.path = (char*)file->url;
958  absolute_uri = apr_uri_unparse(pool, &uri, 0);
959
960  serf_bucket_headers_set(headers, "Destination", absolute_uri);
961
962  serf_bucket_headers_setn(headers, "Overwrite", "F");
963
964  return SVN_NO_ERROR;
965}
966
967static svn_error_t *
968setup_if_header_recursive(svn_boolean_t *added,
969                          serf_bucket_t *headers,
970                          commit_context_t *commit_ctx,
971                          const char *rq_relpath,
972                          apr_pool_t *pool)
973{
974  svn_stringbuf_t *sb = NULL;
975  apr_hash_index_t *hi;
976  apr_pool_t *iterpool = NULL;
977
978  if (!commit_ctx->lock_tokens)
979    {
980      *added = FALSE;
981      return SVN_NO_ERROR;
982    }
983
984  /* We try to create a directory, so within the Subversion world that
985     would imply that there is nothing here, but mod_dav_svn still sees
986     locks on the old nodes here as in DAV it is perfectly legal to lock
987     something that is not there...
988
989     Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
990     the locks we know of with the request */
991
992  for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
993       hi;
994       hi = apr_hash_next(hi))
995    {
996      const char *relpath = apr_hash_this_key(hi);
997      apr_uri_t uri;
998
999      if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
1000        continue;
1001      else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
1002        {
1003          /* When a path is already explicit deleted then its lock
1004             will be removed by mod_dav. But mod_dav doesn't remove
1005             locks on descendants */
1006          continue;
1007        }
1008
1009      if (!iterpool)
1010        iterpool = svn_pool_create(pool);
1011      else
1012        svn_pool_clear(iterpool);
1013
1014      if (sb == NULL)
1015        sb = svn_stringbuf_create("", pool);
1016      else
1017        svn_stringbuf_appendbyte(sb, ' ');
1018
1019      uri = commit_ctx->session->session_url;
1020      uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
1021                                                    iterpool);
1022
1023      svn_stringbuf_appendbyte(sb, '<');
1024      svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1025      svn_stringbuf_appendcstr(sb, "> (<");
1026      svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi));
1027      svn_stringbuf_appendcstr(sb, ">)");
1028    }
1029
1030  if (iterpool)
1031    svn_pool_destroy(iterpool);
1032
1033  if (sb)
1034    {
1035      serf_bucket_headers_set(headers, "If", sb->data);
1036      *added = TRUE;
1037    }
1038  else
1039    *added = FALSE;
1040
1041  return SVN_NO_ERROR;
1042}
1043
1044static svn_error_t *
1045setup_add_dir_common_headers(serf_bucket_t *headers,
1046                             void *baton,
1047                             apr_pool_t *pool /* request pool */,
1048                             apr_pool_t *scratch_pool)
1049{
1050  dir_context_t *dir = baton;
1051  svn_boolean_t added;
1052
1053  return svn_error_trace(
1054      setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath,
1055                                pool));
1056}
1057
1058static svn_error_t *
1059setup_copy_dir_headers(serf_bucket_t *headers,
1060                       void *baton,
1061                       apr_pool_t *pool /* request pool */,
1062                       apr_pool_t *scratch_pool)
1063{
1064  dir_context_t *dir = baton;
1065  apr_uri_t uri;
1066  const char *absolute_uri;
1067
1068  /* The Dest URI must be absolute.  Bummer. */
1069  uri = dir->commit_ctx->session->session_url;
1070
1071  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1072    {
1073      uri.path = (char *)dir->url;
1074    }
1075  else
1076    {
1077      uri.path = (char *)svn_path_url_add_component2(
1078                                    dir->parent_dir->working_url,
1079                                    dir->name, pool);
1080    }
1081  absolute_uri = apr_uri_unparse(pool, &uri, 0);
1082
1083  serf_bucket_headers_set(headers, "Destination", absolute_uri);
1084
1085  serf_bucket_headers_setn(headers, "Depth", "infinity");
1086  serf_bucket_headers_setn(headers, "Overwrite", "F");
1087
1088  /* Implicitly checkout this dir now. */
1089  dir->working_url = apr_pstrdup(dir->pool, uri.path);
1090
1091  return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool,
1092                                                      scratch_pool));
1093}
1094
1095static svn_error_t *
1096setup_delete_headers(serf_bucket_t *headers,
1097                     void *baton,
1098                     apr_pool_t *pool /* request pool */,
1099                     apr_pool_t *scratch_pool)
1100{
1101  delete_context_t *del = baton;
1102  svn_boolean_t added;
1103
1104  serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1105                          apr_ltoa(pool, del->revision));
1106
1107  if (! del->non_recursive_if)
1108    SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx,
1109                                      del->relpath, pool));
1110  else
1111    {
1112      SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx,
1113                                          del->relpath, pool));
1114      added = TRUE;
1115    }
1116
1117  if (added && del->commit_ctx->keep_locks)
1118    serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1119                                      SVN_DAV_OPTION_KEEP_LOCKS);
1120
1121  return SVN_NO_ERROR;
1122}
1123
1124/* POST against 'me' resource handlers. */
1125
1126/* Implements svn_ra_serf__request_body_delegate_t */
1127static svn_error_t *
1128create_txn_post_body(serf_bucket_t **body_bkt,
1129                     void *baton,
1130                     serf_bucket_alloc_t *alloc,
1131                     apr_pool_t *pool /* request pool */,
1132                     apr_pool_t *scratch_pool)
1133{
1134  apr_hash_t *revprops = baton;
1135  svn_skel_t *request_skel;
1136  svn_stringbuf_t *skel_str;
1137
1138  request_skel = svn_skel__make_empty_list(pool);
1139  if (revprops)
1140    {
1141      svn_skel_t *proplist_skel;
1142
1143      SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1144      svn_skel__prepend(proplist_skel, request_skel);
1145      svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1146      skel_str = svn_skel__unparse(request_skel, pool);
1147      *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1148    }
1149  else
1150    {
1151      *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1152    }
1153
1154  return SVN_NO_ERROR;
1155}
1156
1157/* Implements svn_ra_serf__request_header_delegate_t */
1158static svn_error_t *
1159setup_post_headers(serf_bucket_t *headers,
1160                   void *baton,
1161                   apr_pool_t *pool /* request pool */,
1162                   apr_pool_t *scratch_pool)
1163{
1164#ifdef SVN_DAV_SEND_VTXN_NAME
1165  /* Enable this to exercise the VTXN-NAME code based on a client
1166     supplied transaction name. */
1167  serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1168                          svn_uuid_generate(pool));
1169#endif
1170
1171  return SVN_NO_ERROR;
1172}
1173
1174
1175/* Handler baton for POST request. */
1176typedef struct post_response_ctx_t
1177{
1178  svn_ra_serf__handler_t *handler;
1179  commit_context_t *commit_ctx;
1180} post_response_ctx_t;
1181
1182
1183/* This implements serf_bucket_headers_do_callback_fn_t.   */
1184static int
1185post_headers_iterator_callback(void *baton,
1186                               const char *key,
1187                               const char *val)
1188{
1189  post_response_ctx_t *prc = baton;
1190  commit_context_t *prc_cc = prc->commit_ctx;
1191  svn_ra_serf__session_t *sess = prc_cc->session;
1192
1193  /* If we provided a UUID to the POST request, we should get back
1194     from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1195     expect the SVN_DAV_TXN_NAME_HEADER.  We certainly don't expect to
1196     see both. */
1197
1198  if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1199    {
1200      /* Build out txn and txn-root URLs using the txn name we're
1201         given, and store the whole lot of it in the commit context.  */
1202      prc_cc->txn_url =
1203        svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1204      prc_cc->txn_root_url =
1205        svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1206    }
1207
1208  if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1209    {
1210      /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1211         given, and store the whole lot of it in the commit context.  */
1212      prc_cc->txn_url =
1213        svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1214      prc_cc->txn_root_url =
1215        svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1216    }
1217
1218  return 0;
1219}
1220
1221
1222/* A custom serf_response_handler_t which is mostly a wrapper around
1223   svn_ra_serf__expect_empty_body -- it just notices POST response
1224   headers, too.
1225
1226   Implements svn_ra_serf__response_handler_t */
1227static svn_error_t *
1228post_response_handler(serf_request_t *request,
1229                      serf_bucket_t *response,
1230                      void *baton,
1231                      apr_pool_t *scratch_pool)
1232{
1233  post_response_ctx_t *prc = baton;
1234  serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1235
1236  /* Then see which ones we can discover. */
1237  serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1238
1239  /* Execute the 'real' response handler to XML-parse the repsonse body. */
1240  return svn_ra_serf__expect_empty_body(request, response,
1241                                        prc->handler, scratch_pool);
1242}
1243
1244
1245
1246/* Commit baton callbacks */
1247
1248static svn_error_t *
1249open_root(void *edit_baton,
1250          svn_revnum_t base_revision,
1251          apr_pool_t *dir_pool,
1252          void **root_baton)
1253{
1254  commit_context_t *commit_ctx = edit_baton;
1255  svn_ra_serf__handler_t *handler;
1256  proppatch_context_t *proppatch_ctx;
1257  dir_context_t *dir;
1258  apr_hash_index_t *hi;
1259  const char *proppatch_target = NULL;
1260  apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
1261
1262  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
1263    {
1264      post_response_ctx_t *prc;
1265      const char *rel_path;
1266      svn_boolean_t post_with_revprops
1267        = (NULL != svn_hash_gets(commit_ctx->session->supported_posts,
1268                                 "create-txn-with-props"));
1269
1270      /* Create our activity URL now on the server. */
1271      handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1272
1273      handler->method = "POST";
1274      handler->body_type = SVN_SKEL_MIME_TYPE;
1275      handler->body_delegate = create_txn_post_body;
1276      handler->body_delegate_baton =
1277          post_with_revprops ? commit_ctx->revprop_table : NULL;
1278      handler->header_delegate = setup_post_headers;
1279      handler->header_delegate_baton = NULL;
1280      handler->path = commit_ctx->session->me_resource;
1281
1282      prc = apr_pcalloc(scratch_pool, sizeof(*prc));
1283      prc->handler = handler;
1284      prc->commit_ctx = commit_ctx;
1285
1286      handler->response_handler = post_response_handler;
1287      handler->response_baton = prc;
1288
1289      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1290
1291      if (handler->sline.code != 201)
1292        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1293
1294      if (! (commit_ctx->txn_root_url && commit_ctx->txn_url))
1295        {
1296          return svn_error_createf(
1297            SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1298            _("POST request did not return transaction information"));
1299        }
1300
1301      /* Fixup the txn_root_url to point to the anchor of the commit. */
1302      SVN_ERR(svn_ra_serf__get_relative_path(
1303                                        &rel_path,
1304                                        commit_ctx->session->session_url.path,
1305                                        commit_ctx->session,
1306                                        scratch_pool));
1307      commit_ctx->txn_root_url = svn_path_url_add_component2(
1308                                        commit_ctx->txn_root_url,
1309                                        rel_path, commit_ctx->pool);
1310
1311      /* Build our directory baton. */
1312      dir = apr_pcalloc(dir_pool, sizeof(*dir));
1313      dir->pool = dir_pool;
1314      dir->commit_ctx = commit_ctx;
1315      dir->base_revision = base_revision;
1316      dir->relpath = "";
1317      dir->name = "";
1318      dir->prop_changes = apr_hash_make(dir->pool);
1319      dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url);
1320
1321      /* If we included our revprops in the POST, we need not
1322         PROPPATCH them. */
1323      proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url;
1324    }
1325  else
1326    {
1327      const char *activity_str = commit_ctx->session->activity_collection_url;
1328
1329      if (!activity_str)
1330        SVN_ERR(svn_ra_serf__v1_get_activity_collection(
1331                                    &activity_str,
1332                                    commit_ctx->session,
1333                                    scratch_pool, scratch_pool));
1334
1335      commit_ctx->activity_url = svn_path_url_add_component2(
1336                                    activity_str,
1337                                    svn_uuid_generate(scratch_pool),
1338                                    commit_ctx->pool);
1339
1340      /* Create our activity URL now on the server. */
1341      handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1342
1343      handler->method = "MKACTIVITY";
1344      handler->path = commit_ctx->activity_url;
1345
1346      handler->response_handler = svn_ra_serf__expect_empty_body;
1347      handler->response_baton = handler;
1348
1349      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1350
1351      if (handler->sline.code != 201)
1352        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1353
1354      /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1355      SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url),
1356                                        commit_ctx->session, scratch_pool));
1357
1358
1359      /* Build our directory baton. */
1360      dir = apr_pcalloc(dir_pool, sizeof(*dir));
1361      dir->pool = dir_pool;
1362      dir->commit_ctx = commit_ctx;
1363      dir->base_revision = base_revision;
1364      dir->relpath = "";
1365      dir->name = "";
1366      dir->prop_changes = apr_hash_make(dir->pool);
1367
1368      SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session,
1369                              dir->relpath,
1370                              dir->base_revision, commit_ctx->checked_in_url,
1371                              dir->pool, scratch_pool));
1372      commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url);
1373
1374      /* Checkout our root dir */
1375      SVN_ERR(checkout_dir(dir, scratch_pool));
1376
1377      proppatch_target = commit_ctx->baseline_url;
1378    }
1379
1380  /* Unless this is NULL -- which means we don't need to PROPPATCH the
1381     transaction with our revprops -- then, you know, PROPPATCH the
1382     transaction with our revprops.  */
1383  if (proppatch_target)
1384    {
1385      proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx));
1386      proppatch_ctx->pool = scratch_pool;
1387      proppatch_ctx->commit_ctx = NULL; /* No lock info */
1388      proppatch_ctx->path = proppatch_target;
1389      proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool);
1390      proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1391
1392      for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table);
1393           hi;
1394           hi = apr_hash_next(hi))
1395        {
1396          svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop));
1397
1398          prop->name = apr_hash_this_key(hi);
1399          prop->value = apr_hash_this_val(hi);
1400
1401          svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
1402        }
1403
1404      SVN_ERR(proppatch_resource(commit_ctx->session,
1405                                 proppatch_ctx, scratch_pool));
1406    }
1407
1408  svn_pool_destroy(scratch_pool);
1409
1410  *root_baton = dir;
1411
1412  return SVN_NO_ERROR;
1413}
1414
1415/* Implements svn_ra_serf__request_body_delegate_t */
1416static svn_error_t *
1417create_delete_body(serf_bucket_t **body_bkt,
1418                   void *baton,
1419                   serf_bucket_alloc_t *alloc,
1420                   apr_pool_t *pool /* request pool */,
1421                   apr_pool_t *scratch_pool)
1422{
1423  delete_context_t *ctx = baton;
1424  serf_bucket_t *body;
1425
1426  body = serf_bucket_aggregate_create(alloc);
1427
1428  svn_ra_serf__add_xml_header_buckets(body, alloc);
1429
1430  svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens,
1431                                     ctx->relpath, body, alloc, pool);
1432
1433  *body_bkt = body;
1434  return SVN_NO_ERROR;
1435}
1436
1437static svn_error_t *
1438delete_entry(const char *path,
1439             svn_revnum_t revision,
1440             void *parent_baton,
1441             apr_pool_t *pool)
1442{
1443  dir_context_t *dir = parent_baton;
1444  delete_context_t *delete_ctx;
1445  svn_ra_serf__handler_t *handler;
1446  const char *delete_target;
1447
1448  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1449    {
1450      delete_target = svn_path_url_add_component2(
1451                                    dir->commit_ctx->txn_root_url,
1452                                    path, dir->pool);
1453    }
1454  else
1455    {
1456      /* Ensure our directory has been checked out */
1457      SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1458      delete_target = svn_path_url_add_component2(dir->working_url,
1459                                                  svn_relpath_basename(path,
1460                                                                       NULL),
1461                                                  pool);
1462    }
1463
1464  /* DELETE our entry */
1465  delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1466  delete_ctx->relpath = apr_pstrdup(pool, path);
1467  delete_ctx->revision = revision;
1468  delete_ctx->commit_ctx = dir->commit_ctx;
1469
1470  handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1471
1472  handler->response_handler = svn_ra_serf__expect_empty_body;
1473  handler->response_baton = handler;
1474
1475  handler->header_delegate = setup_delete_headers;
1476  handler->header_delegate_baton = delete_ctx;
1477
1478  handler->method = "DELETE";
1479  handler->path = delete_target;
1480  handler->no_fail_on_http_failure_status = TRUE;
1481
1482  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1483
1484  if (handler->sline.code == 400)
1485    {
1486      /* Try again with non-standard body to overcome Apache Httpd
1487         header limit */
1488      delete_ctx->non_recursive_if = TRUE;
1489
1490      handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1491
1492      handler->response_handler = svn_ra_serf__expect_empty_body;
1493      handler->response_baton = handler;
1494
1495      handler->header_delegate = setup_delete_headers;
1496      handler->header_delegate_baton = delete_ctx;
1497
1498      handler->method = "DELETE";
1499      handler->path = delete_target;
1500
1501      handler->body_type = "text/xml";
1502      handler->body_delegate = create_delete_body;
1503      handler->body_delegate_baton = delete_ctx;
1504
1505      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1506    }
1507
1508  if (handler->server_error)
1509    return svn_ra_serf__server_error_create(handler, pool);
1510
1511  /* 204 No Content: item successfully deleted */
1512  if (handler->sline.code != 204)
1513    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1514
1515  svn_hash_sets(dir->commit_ctx->deleted_entries,
1516                apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
1517
1518  return SVN_NO_ERROR;
1519}
1520
1521static svn_error_t *
1522add_directory(const char *path,
1523              void *parent_baton,
1524              const char *copyfrom_path,
1525              svn_revnum_t copyfrom_revision,
1526              apr_pool_t *dir_pool,
1527              void **child_baton)
1528{
1529  dir_context_t *parent = parent_baton;
1530  dir_context_t *dir;
1531  svn_ra_serf__handler_t *handler;
1532  apr_status_t status;
1533  const char *mkcol_target;
1534
1535  dir = apr_pcalloc(dir_pool, sizeof(*dir));
1536
1537  dir->pool = dir_pool;
1538  dir->parent_dir = parent;
1539  dir->commit_ctx = parent->commit_ctx;
1540  dir->added = TRUE;
1541  dir->base_revision = SVN_INVALID_REVNUM;
1542  dir->copy_revision = copyfrom_revision;
1543  dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1544  dir->relpath = apr_pstrdup(dir->pool, path);
1545  dir->name = svn_relpath_basename(dir->relpath, NULL);
1546  dir->prop_changes = apr_hash_make(dir->pool);
1547
1548  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1549    {
1550      dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1551                                             path, dir->pool);
1552      mkcol_target = dir->url;
1553    }
1554  else
1555    {
1556      /* Ensure our parent is checked out. */
1557      SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1558
1559      dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
1560                                             dir->name, dir->pool);
1561      mkcol_target = svn_path_url_add_component2(
1562                               parent->working_url,
1563                               dir->name, dir->pool);
1564    }
1565
1566  handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
1567
1568  handler->response_handler = svn_ra_serf__expect_empty_body;
1569  handler->response_baton = handler;
1570  if (!dir->copy_path)
1571    {
1572      handler->method = "MKCOL";
1573      handler->path = mkcol_target;
1574
1575      handler->header_delegate = setup_add_dir_common_headers;
1576      handler->header_delegate_baton = dir;
1577    }
1578  else
1579    {
1580      apr_uri_t uri;
1581      const char *req_url;
1582
1583      status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1584      if (status)
1585        {
1586          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1587                                   _("Unable to parse URL '%s'"),
1588                                   dir->copy_path);
1589        }
1590
1591      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1592                                          dir->commit_ctx->session,
1593                                          uri.path, dir->copy_revision,
1594                                          dir_pool, dir_pool));
1595
1596      handler->method = "COPY";
1597      handler->path = req_url;
1598
1599      handler->header_delegate = setup_copy_dir_headers;
1600      handler->header_delegate_baton = dir;
1601    }
1602  /* We have the same problem as with DELETE here: if there are too many
1603     locks, the request fails. But in this case there is no way to retry
1604     with a non-standard request. #### How to fix? */
1605  SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1606
1607  if (handler->sline.code != 201)
1608    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1609
1610  *child_baton = dir;
1611
1612  return SVN_NO_ERROR;
1613}
1614
1615static svn_error_t *
1616open_directory(const char *path,
1617               void *parent_baton,
1618               svn_revnum_t base_revision,
1619               apr_pool_t *dir_pool,
1620               void **child_baton)
1621{
1622  dir_context_t *parent = parent_baton;
1623  dir_context_t *dir;
1624
1625  dir = apr_pcalloc(dir_pool, sizeof(*dir));
1626
1627  dir->pool = dir_pool;
1628
1629  dir->parent_dir = parent;
1630  dir->commit_ctx = parent->commit_ctx;
1631
1632  dir->added = FALSE;
1633  dir->base_revision = base_revision;
1634  dir->relpath = apr_pstrdup(dir->pool, path);
1635  dir->name = svn_relpath_basename(dir->relpath, NULL);
1636  dir->prop_changes = apr_hash_make(dir->pool);
1637
1638  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1639    {
1640      dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1641                                             path, dir->pool);
1642    }
1643  else
1644    {
1645      SVN_ERR(get_version_url(&dir->url,
1646                              dir->commit_ctx->session,
1647                              dir->relpath, dir->base_revision,
1648                              dir->commit_ctx->checked_in_url,
1649                              dir->pool, dir->pool /* scratch_pool */));
1650    }
1651  *child_baton = dir;
1652
1653  return SVN_NO_ERROR;
1654}
1655
1656static svn_error_t *
1657change_dir_prop(void *dir_baton,
1658                const char *name,
1659                const svn_string_t *value,
1660                apr_pool_t *scratch_pool)
1661{
1662  dir_context_t *dir = dir_baton;
1663  svn_prop_t *prop;
1664
1665  if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1666    {
1667      /* Ensure we have a checked out dir. */
1668      SVN_ERR(checkout_dir(dir, scratch_pool));
1669    }
1670
1671  prop = apr_palloc(dir->pool, sizeof(*prop));
1672
1673  prop->name = apr_pstrdup(dir->pool, name);
1674  prop->value = svn_string_dup(value, dir->pool);
1675
1676  svn_hash_sets(dir->prop_changes, prop->name, prop);
1677
1678  return SVN_NO_ERROR;
1679}
1680
1681static svn_error_t *
1682close_directory(void *dir_baton,
1683                apr_pool_t *pool)
1684{
1685  dir_context_t *dir = dir_baton;
1686
1687  /* Huh?  We're going to be called before the texts are sent.  Ugh.
1688   * Therefore, just wave politely at our caller.
1689   */
1690
1691  /* PROPPATCH our prop change and pass it along.  */
1692  if (apr_hash_count(dir->prop_changes))
1693    {
1694      proppatch_context_t *proppatch_ctx;
1695
1696      proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1697      proppatch_ctx->pool = pool;
1698      proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
1699      proppatch_ctx->relpath = dir->relpath;
1700      proppatch_ctx->prop_changes = dir->prop_changes;
1701      proppatch_ctx->base_revision = dir->base_revision;
1702
1703      if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1704        {
1705          proppatch_ctx->path = dir->url;
1706        }
1707      else
1708        {
1709          proppatch_ctx->path = dir->working_url;
1710        }
1711
1712      SVN_ERR(proppatch_resource(dir->commit_ctx->session,
1713                                 proppatch_ctx, dir->pool));
1714    }
1715
1716  return SVN_NO_ERROR;
1717}
1718
1719static svn_error_t *
1720add_file(const char *path,
1721         void *parent_baton,
1722         const char *copy_path,
1723         svn_revnum_t copy_revision,
1724         apr_pool_t *file_pool,
1725         void **file_baton)
1726{
1727  dir_context_t *dir = parent_baton;
1728  file_context_t *new_file;
1729  const char *deleted_parent = path;
1730  apr_pool_t *scratch_pool = svn_pool_create(file_pool);
1731
1732  new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1733  new_file->pool = file_pool;
1734
1735  dir->ref_count++;
1736
1737  new_file->parent_dir = dir;
1738  new_file->commit_ctx = dir->commit_ctx;
1739  new_file->relpath = apr_pstrdup(new_file->pool, path);
1740  new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1741  new_file->added = TRUE;
1742  new_file->base_revision = SVN_INVALID_REVNUM;
1743  new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1744  new_file->copy_revision = copy_revision;
1745  new_file->prop_changes = apr_hash_make(new_file->pool);
1746
1747  /* Ensure that the file doesn't exist by doing a HEAD on the
1748     resource.  If we're using HTTP v2, we'll just look into the
1749     transaction root tree for this thing.  */
1750  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1751    {
1752      new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
1753                                                  path, new_file->pool);
1754    }
1755  else
1756    {
1757      /* Ensure our parent directory has been checked out */
1758      SVN_ERR(checkout_dir(dir, scratch_pool));
1759
1760      new_file->url =
1761        svn_path_url_add_component2(dir->working_url,
1762                                    new_file->name, new_file->pool);
1763    }
1764
1765  while (deleted_parent && deleted_parent[0] != '\0')
1766    {
1767      if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
1768        {
1769          break;
1770        }
1771      deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1772    }
1773
1774  if (copy_path)
1775    {
1776      svn_ra_serf__handler_t *handler;
1777      apr_uri_t uri;
1778      const char *req_url;
1779      apr_status_t status;
1780
1781      /* Create the copy directly as cheap 'does exist/out of date'
1782         check. We update the copy (if needed) from close_file() */
1783
1784      status = apr_uri_parse(scratch_pool, copy_path, &uri);
1785      if (status)
1786        return svn_ra_serf__wrap_err(status, NULL);
1787
1788      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1789                                          dir->commit_ctx->session,
1790                                          uri.path, copy_revision,
1791                                          scratch_pool, scratch_pool));
1792
1793      handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1794                                            scratch_pool);
1795      handler->method = "COPY";
1796      handler->path = req_url;
1797
1798      handler->response_handler = svn_ra_serf__expect_empty_body;
1799      handler->response_baton = handler;
1800
1801      handler->header_delegate = setup_copy_file_headers;
1802      handler->header_delegate_baton = new_file;
1803
1804      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1805
1806      if (handler->sline.code != 201)
1807        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1808    }
1809  else if (! ((dir->added && !dir->copy_path) ||
1810           (deleted_parent && deleted_parent[0] != '\0')))
1811    {
1812      svn_ra_serf__handler_t *handler;
1813      svn_error_t *err;
1814
1815      handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1816                                            scratch_pool);
1817      handler->method = "HEAD";
1818      handler->path = svn_path_url_add_component2(
1819                                        dir->commit_ctx->session->session_url.path,
1820                                        path, scratch_pool);
1821      handler->response_handler = svn_ra_serf__expect_empty_body;
1822      handler->response_baton = handler;
1823      handler->no_dav_headers = TRUE; /* Read only operation outside txn */
1824
1825      err = svn_ra_serf__context_run_one(handler, scratch_pool);
1826
1827      if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1828        {
1829          svn_error_clear(err); /* Great. We can create a new file! */
1830        }
1831      else if (err)
1832        return svn_error_trace(err);
1833      else
1834        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1835                                 _("File '%s' already exists"), path);
1836    }
1837
1838  svn_pool_destroy(scratch_pool);
1839  *file_baton = new_file;
1840
1841  return SVN_NO_ERROR;
1842}
1843
1844static svn_error_t *
1845open_file(const char *path,
1846          void *parent_baton,
1847          svn_revnum_t base_revision,
1848          apr_pool_t *file_pool,
1849          void **file_baton)
1850{
1851  dir_context_t *parent = parent_baton;
1852  file_context_t *new_file;
1853
1854  new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1855  new_file->pool = file_pool;
1856
1857  parent->ref_count++;
1858
1859  new_file->parent_dir = parent;
1860  new_file->commit_ctx = parent->commit_ctx;
1861  new_file->relpath = apr_pstrdup(new_file->pool, path);
1862  new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1863  new_file->added = FALSE;
1864  new_file->base_revision = base_revision;
1865  new_file->prop_changes = apr_hash_make(new_file->pool);
1866
1867  if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
1868    {
1869      new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1870                                                  path, new_file->pool);
1871    }
1872  else
1873    {
1874      /* CHECKOUT the file into our activity. */
1875      SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1876
1877      new_file->url = new_file->working_url;
1878    }
1879
1880  *file_baton = new_file;
1881
1882  return SVN_NO_ERROR;
1883}
1884
1885/* Implements svn_stream_lazyopen_func_t for apply_textdelta */
1886static svn_error_t *
1887delayed_commit_stream_open(svn_stream_t **stream,
1888                           void *baton,
1889                           apr_pool_t *result_pool,
1890                           apr_pool_t *scratch_pool)
1891{
1892  file_context_t *file_ctx = baton;
1893
1894  SVN_ERR(svn_io_open_unique_file3(&file_ctx->svndiff, NULL, NULL,
1895                                   svn_io_file_del_on_pool_cleanup,
1896                                   file_ctx->pool, scratch_pool));
1897
1898  *stream = svn_stream_from_aprfile2(file_ctx->svndiff, TRUE, result_pool);
1899  return SVN_NO_ERROR;
1900}
1901
1902static svn_error_t *
1903apply_textdelta(void *file_baton,
1904                const char *base_checksum,
1905                apr_pool_t *pool,
1906                svn_txdelta_window_handler_t *handler,
1907                void **handler_baton)
1908{
1909  file_context_t *ctx = file_baton;
1910
1911  /* Store the stream in a temporary file; we'll give it to serf when we
1912   * close this file.
1913   *
1914   * TODO: There should be a way we can stream the request body instead of
1915   * writing to a temporary file (ugh). A special svn stream serf bucket
1916   * that returns EAGAIN until we receive the done call?  But, when
1917   * would we run through the serf context?  Grr.
1918   */
1919
1920  ctx->stream = svn_stream_lazyopen_create(delayed_commit_stream_open,
1921                                           ctx, FALSE, ctx->pool);
1922
1923  svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
1924                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
1925
1926  if (base_checksum)
1927    ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
1928
1929  return SVN_NO_ERROR;
1930}
1931
1932static svn_error_t *
1933change_file_prop(void *file_baton,
1934                 const char *name,
1935                 const svn_string_t *value,
1936                 apr_pool_t *pool)
1937{
1938  file_context_t *file = file_baton;
1939  svn_prop_t *prop;
1940
1941  prop = apr_palloc(file->pool, sizeof(*prop));
1942
1943  prop->name = apr_pstrdup(file->pool, name);
1944  prop->value = svn_string_dup(value, file->pool);
1945
1946  svn_hash_sets(file->prop_changes, prop->name, prop);
1947
1948  return SVN_NO_ERROR;
1949}
1950
1951static svn_error_t *
1952close_file(void *file_baton,
1953           const char *text_checksum,
1954           apr_pool_t *scratch_pool)
1955{
1956  file_context_t *ctx = file_baton;
1957  svn_boolean_t put_empty_file = FALSE;
1958
1959  ctx->result_checksum = text_checksum;
1960
1961  /* If we got no stream of changes, but this is an added-without-history
1962   * file, make a note that we'll be PUTting a zero-byte file to the server.
1963   */
1964  if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
1965    put_empty_file = TRUE;
1966
1967  /* If we had a stream of changes, push them to the server... */
1968  if (ctx->svndiff || put_empty_file)
1969    {
1970      svn_ra_serf__handler_t *handler;
1971      int expected_result;
1972
1973      handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
1974                                            scratch_pool);
1975
1976      handler->method = "PUT";
1977      handler->path = ctx->url;
1978
1979      handler->response_handler = svn_ra_serf__expect_empty_body;
1980      handler->response_baton = handler;
1981
1982      if (put_empty_file)
1983        {
1984          handler->body_delegate = create_empty_put_body;
1985          handler->body_delegate_baton = ctx;
1986          handler->body_type = "text/plain";
1987        }
1988      else
1989        {
1990          handler->body_delegate = create_put_body;
1991          handler->body_delegate_baton = ctx;
1992          handler->body_type = SVN_SVNDIFF_MIME_TYPE;
1993        }
1994
1995      handler->header_delegate = setup_put_headers;
1996      handler->header_delegate_baton = ctx;
1997
1998      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1999
2000      if (ctx->added && ! ctx->copy_path)
2001        expected_result = 201; /* Created */
2002      else
2003        expected_result = 204; /* Updated */
2004
2005      if (handler->sline.code != expected_result)
2006        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2007    }
2008
2009  if (ctx->svndiff)
2010    SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2011
2012  /* If we had any prop changes, push them via PROPPATCH. */
2013  if (apr_hash_count(ctx->prop_changes))
2014    {
2015      proppatch_context_t *proppatch;
2016
2017      proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
2018      proppatch->pool = scratch_pool;
2019      proppatch->relpath = ctx->relpath;
2020      proppatch->path = ctx->url;
2021      proppatch->commit_ctx = ctx->commit_ctx;
2022      proppatch->prop_changes = ctx->prop_changes;
2023      proppatch->base_revision = ctx->base_revision;
2024
2025      SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
2026                                 proppatch, scratch_pool));
2027    }
2028
2029  return SVN_NO_ERROR;
2030}
2031
2032static svn_error_t *
2033close_edit(void *edit_baton,
2034           apr_pool_t *pool)
2035{
2036  commit_context_t *ctx = edit_baton;
2037  const char *merge_target =
2038    ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2039  const svn_commit_info_t *commit_info;
2040  svn_error_t *err = NULL;
2041
2042  /* MERGE our activity */
2043  SVN_ERR(svn_ra_serf__run_merge(&commit_info,
2044                                 ctx->session,
2045                                 merge_target,
2046                                 ctx->lock_tokens,
2047                                 ctx->keep_locks,
2048                                 pool, pool));
2049
2050  ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
2051
2052  /* Inform the WC that we did a commit.  */
2053  if (ctx->callback)
2054    err = ctx->callback(commit_info, ctx->callback_baton, pool);
2055
2056  /* If we're using activities, DELETE our completed activity.  */
2057  if (ctx->activity_url)
2058    {
2059      svn_ra_serf__handler_t *handler;
2060
2061      handler = svn_ra_serf__create_handler(ctx->session, pool);
2062
2063      handler->method = "DELETE";
2064      handler->path = ctx->activity_url;
2065
2066      handler->response_handler = svn_ra_serf__expect_empty_body;
2067      handler->response_baton = handler;
2068
2069      ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
2070
2071      SVN_ERR(svn_error_compose_create(
2072                  err,
2073                  svn_ra_serf__context_run_one(handler, pool)));
2074
2075      if (handler->sline.code != 204)
2076        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2077    }
2078
2079  SVN_ERR(err);
2080
2081  return SVN_NO_ERROR;
2082}
2083
2084static svn_error_t *
2085abort_edit(void *edit_baton,
2086           apr_pool_t *pool)
2087{
2088  commit_context_t *ctx = edit_baton;
2089  svn_ra_serf__handler_t *handler;
2090
2091  /* If an activity or transaction wasn't even created, don't bother
2092     trying to delete it. */
2093  if (! (ctx->activity_url || ctx->txn_url))
2094    return SVN_NO_ERROR;
2095
2096  /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2097     had a problem. We need to reset it, in order to use it again.  */
2098  serf_connection_reset(ctx->session->conns[0]->conn);
2099
2100  /* DELETE our aborted activity */
2101  handler = svn_ra_serf__create_handler(ctx->session, pool);
2102
2103  handler->method = "DELETE";
2104
2105  handler->response_handler = svn_ra_serf__expect_empty_body;
2106  handler->response_baton = handler;
2107  handler->no_fail_on_http_failure_status = TRUE;
2108
2109  if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2110    handler->path = ctx->txn_url;
2111  else
2112    handler->path = ctx->activity_url;
2113
2114  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2115
2116  /* 204 if deleted,
2117     403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2118     404 if the activity wasn't found. */
2119  if (handler->sline.code != 204
2120      && handler->sline.code != 403
2121      && handler->sline.code != 404)
2122    {
2123      return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2124    }
2125
2126  /* Don't delete again if somebody aborts twice */
2127  ctx->activity_url = NULL;
2128  ctx->txn_url = NULL;
2129
2130  return SVN_NO_ERROR;
2131}
2132
2133svn_error_t *
2134svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2135                               const svn_delta_editor_t **ret_editor,
2136                               void **edit_baton,
2137                               apr_hash_t *revprop_table,
2138                               svn_commit_callback2_t callback,
2139                               void *callback_baton,
2140                               apr_hash_t *lock_tokens,
2141                               svn_boolean_t keep_locks,
2142                               apr_pool_t *pool)
2143{
2144  svn_ra_serf__session_t *session = ra_session->priv;
2145  svn_delta_editor_t *editor;
2146  commit_context_t *ctx;
2147  const char *repos_root;
2148  const char *base_relpath;
2149  svn_boolean_t supports_ephemeral_props;
2150
2151  ctx = apr_pcalloc(pool, sizeof(*ctx));
2152
2153  ctx->pool = pool;
2154
2155  ctx->session = session;
2156
2157  ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2158
2159  /* If the server supports ephemeral properties, add some carrying
2160     interesting version information. */
2161  SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2162                                      SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2163                                      pool));
2164  if (supports_ephemeral_props)
2165    {
2166      svn_hash_sets(ctx->revprop_table,
2167                    apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2168                    svn_string_create(SVN_VER_NUMBER, pool));
2169      svn_hash_sets(ctx->revprop_table,
2170                    apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2171                    svn_string_create(session->useragent, pool));
2172    }
2173
2174  ctx->callback = callback;
2175  ctx->callback_baton = callback_baton;
2176
2177  ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2178                       ? lock_tokens : NULL;
2179  ctx->keep_locks = keep_locks;
2180
2181  ctx->deleted_entries = apr_hash_make(ctx->pool);
2182
2183  editor = svn_delta_default_editor(pool);
2184  editor->open_root = open_root;
2185  editor->delete_entry = delete_entry;
2186  editor->add_directory = add_directory;
2187  editor->open_directory = open_directory;
2188  editor->change_dir_prop = change_dir_prop;
2189  editor->close_directory = close_directory;
2190  editor->add_file = add_file;
2191  editor->open_file = open_file;
2192  editor->apply_textdelta = apply_textdelta;
2193  editor->change_file_prop = change_file_prop;
2194  editor->close_file = close_file;
2195  editor->close_edit = close_edit;
2196  editor->abort_edit = abort_edit;
2197
2198  *ret_editor = editor;
2199  *edit_baton = ctx;
2200
2201  SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2202  base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2203                                       pool);
2204
2205  SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2206                                   *edit_baton, repos_root, base_relpath,
2207                                   session->shim_callbacks, pool, pool));
2208
2209  return SVN_NO_ERROR;
2210}
2211
2212svn_error_t *
2213svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2214                             svn_revnum_t rev,
2215                             const char *name,
2216                             const svn_string_t *const *old_value_p,
2217                             const svn_string_t *value,
2218                             apr_pool_t *pool)
2219{
2220  svn_ra_serf__session_t *session = ra_session->priv;
2221  proppatch_context_t *proppatch_ctx;
2222  const char *proppatch_target;
2223  const svn_string_t *tmp_old_value;
2224  svn_boolean_t atomic_capable = FALSE;
2225  svn_prop_t *prop;
2226  svn_error_t *err;
2227
2228  if (old_value_p || !value)
2229    SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable,
2230                                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2231                                        pool));
2232
2233  if (old_value_p)
2234    {
2235      /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2236      SVN_ERR_ASSERT(atomic_capable);
2237    }
2238  else if (! value && atomic_capable)
2239    {
2240      svn_string_t *old_value;
2241      /* mod_dav_svn doesn't report a failure when a property delete fails. The
2242         atomic revprop change behavior is a nice workaround, to allow getting
2243         access to the error anyway.
2244
2245         Somehow the mod_dav maintainers think that returning an error from
2246         mod_dav's property delete is an RFC violation.
2247         See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */
2248
2249      SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value,
2250                                    pool));
2251
2252      if (!old_value)
2253        return SVN_NO_ERROR; /* Nothing to delete */
2254
2255      /* The api expects a double const pointer. Let's make one */
2256      tmp_old_value = old_value;
2257      old_value_p = &tmp_old_value;
2258    }
2259
2260  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2261    {
2262      proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2263    }
2264  else
2265    {
2266      const char *vcc_url;
2267
2268      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
2269
2270      SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2271                                          session, vcc_url, rev, "href",
2272                                          pool, pool));
2273    }
2274
2275  /* PROPPATCH our log message and pass it along.  */
2276  proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2277  proppatch_ctx->pool = pool;
2278  proppatch_ctx->commit_ctx = NULL; /* No lock headers */
2279  proppatch_ctx->path = proppatch_target;
2280  proppatch_ctx->prop_changes = apr_hash_make(pool);
2281  proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2282
2283  if (old_value_p)
2284    {
2285      prop = apr_palloc(pool, sizeof (*prop));
2286
2287      prop->name = name;
2288      prop->value = *old_value_p;
2289
2290      proppatch_ctx->old_props = apr_hash_make(pool);
2291      svn_hash_sets(proppatch_ctx->old_props, prop->name, prop);
2292    }
2293
2294  prop = apr_palloc(pool, sizeof (*prop));
2295
2296  prop->name = name;
2297  prop->value = value;
2298  svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
2299
2300  err = proppatch_resource(session, proppatch_ctx, pool);
2301
2302  /* Use specific error code for old property value mismatch.
2303     Use loop to provide the right result with tracing */
2304  if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2305    {
2306      svn_error_t *e = err;
2307
2308      while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2309        {
2310          e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
2311          e = e->child;
2312        }
2313    }
2314
2315  return svn_error_trace(err);
2316}
2317