1251881Speter/*
2251881Speter * commit.c :  entry point for commit RA functions for ra_serf
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include <apr_uri.h>
25251881Speter#include <serf.h>
26251881Speter
27251881Speter#include "svn_hash.h"
28251881Speter#include "svn_pools.h"
29251881Speter#include "svn_ra.h"
30251881Speter#include "svn_dav.h"
31251881Speter#include "svn_xml.h"
32251881Speter#include "svn_config.h"
33251881Speter#include "svn_delta.h"
34251881Speter#include "svn_base64.h"
35251881Speter#include "svn_dirent_uri.h"
36251881Speter#include "svn_path.h"
37251881Speter#include "svn_props.h"
38251881Speter
39251881Speter#include "svn_private_config.h"
40251881Speter#include "private/svn_dep_compat.h"
41251881Speter#include "private/svn_fspath.h"
42251881Speter#include "private/svn_skel.h"
43251881Speter
44251881Speter#include "ra_serf.h"
45251881Speter#include "../libsvn_ra/ra_loader.h"
46251881Speter
47251881Speter
48251881Speter/* Baton passed back with the commit editor. */
49251881Spetertypedef struct commit_context_t {
50251881Speter  /* Pool for our commit. */
51251881Speter  apr_pool_t *pool;
52251881Speter
53251881Speter  svn_ra_serf__session_t *session;
54251881Speter
55251881Speter  apr_hash_t *revprop_table;
56251881Speter
57251881Speter  svn_commit_callback2_t callback;
58251881Speter  void *callback_baton;
59251881Speter
60251881Speter  apr_hash_t *lock_tokens;
61251881Speter  svn_boolean_t keep_locks;
62251881Speter  apr_hash_t *deleted_entries;   /* deleted files (for delete+add detection) */
63251881Speter
64251881Speter  /* HTTP v2 stuff */
65251881Speter  const char *txn_url;           /* txn URL (!svn/txn/TXN_NAME) */
66251881Speter  const char *txn_root_url;      /* commit anchor txn root URL */
67251881Speter
68251881Speter  /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
69251881Speter  const char *activity_url;      /* activity base URL... */
70251881Speter  const char *baseline_url;      /* the working-baseline resource */
71251881Speter  const char *checked_in_url;    /* checked-in root to base CHECKOUTs from */
72251881Speter  const char *vcc_url;           /* vcc url */
73251881Speter
74251881Speter} commit_context_t;
75251881Speter
76251881Speter#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
77251881Speter
78251881Speter/* Structure associated with a PROPPATCH request. */
79251881Spetertypedef struct proppatch_context_t {
80251881Speter  apr_pool_t *pool;
81251881Speter
82251881Speter  const char *relpath;
83251881Speter  const char *path;
84251881Speter
85299742Sdim  commit_context_t *commit_ctx;
86251881Speter
87299742Sdim  /* Changed properties. const char * -> svn_prop_t * */
88299742Sdim  apr_hash_t *prop_changes;
89251881Speter
90299742Sdim  /* Same, for the old value, or NULL. */
91299742Sdim  apr_hash_t *old_props;
92251881Speter
93251881Speter  /* In HTTP v2, this is the file/directory version we think we're changing. */
94251881Speter  svn_revnum_t base_revision;
95251881Speter
96251881Speter} proppatch_context_t;
97251881Speter
98251881Spetertypedef struct delete_context_t {
99269847Speter  const char *relpath;
100251881Speter
101251881Speter  svn_revnum_t revision;
102251881Speter
103299742Sdim  commit_context_t *commit_ctx;
104299742Sdim
105299742Sdim  svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */
106251881Speter} delete_context_t;
107251881Speter
108251881Speter/* Represents a directory. */
109251881Spetertypedef struct dir_context_t {
110251881Speter  /* Pool for our directory. */
111251881Speter  apr_pool_t *pool;
112251881Speter
113251881Speter  /* The root commit we're in progress for. */
114299742Sdim  commit_context_t *commit_ctx;
115251881Speter
116251881Speter  /* URL to operate against (used for CHECKOUT and PROPPATCH before
117251881Speter     HTTP v2, for PROPPATCH in HTTP v2).  */
118251881Speter  const char *url;
119251881Speter
120251881Speter  /* How many pending changes we have left in this directory. */
121251881Speter  unsigned int ref_count;
122251881Speter
123251881Speter  /* Is this directory being added?  (Otherwise, just opened.) */
124251881Speter  svn_boolean_t added;
125251881Speter
126251881Speter  /* Our parent */
127251881Speter  struct dir_context_t *parent_dir;
128251881Speter
129251881Speter  /* The directory name; if "", we're the 'root' */
130251881Speter  const char *relpath;
131251881Speter
132251881Speter  /* The basename of the directory. "" for the 'root' */
133251881Speter  const char *name;
134251881Speter
135251881Speter  /* The base revision of the dir. */
136251881Speter  svn_revnum_t base_revision;
137251881Speter
138251881Speter  const char *copy_path;
139251881Speter  svn_revnum_t copy_revision;
140251881Speter
141299742Sdim  /* Changed properties (const char * -> svn_prop_t *) */
142299742Sdim  apr_hash_t *prop_changes;
143251881Speter
144251881Speter  /* The checked-out working resource for this directory.  May be NULL; if so
145251881Speter     call checkout_dir() first.  */
146251881Speter  const char *working_url;
147251881Speter} dir_context_t;
148251881Speter
149251881Speter/* Represents a file to be committed. */
150251881Spetertypedef struct file_context_t {
151251881Speter  /* Pool for our file. */
152251881Speter  apr_pool_t *pool;
153251881Speter
154251881Speter  /* The root commit we're in progress for. */
155299742Sdim  commit_context_t *commit_ctx;
156251881Speter
157251881Speter  /* Is this file being added?  (Otherwise, just opened.) */
158251881Speter  svn_boolean_t added;
159251881Speter
160251881Speter  dir_context_t *parent_dir;
161251881Speter
162251881Speter  const char *relpath;
163251881Speter  const char *name;
164251881Speter
165251881Speter  /* The checked-out working resource for this file. */
166251881Speter  const char *working_url;
167251881Speter
168251881Speter  /* The base revision of the file. */
169251881Speter  svn_revnum_t base_revision;
170251881Speter
171251881Speter  /* Copy path and revision */
172251881Speter  const char *copy_path;
173251881Speter  svn_revnum_t copy_revision;
174251881Speter
175251881Speter  /* stream */
176251881Speter  svn_stream_t *stream;
177251881Speter
178251881Speter  /* Temporary file containing the svndiff. */
179251881Speter  apr_file_t *svndiff;
180251881Speter
181251881Speter  /* Our base checksum as reported by the WC. */
182251881Speter  const char *base_checksum;
183251881Speter
184251881Speter  /* Our resulting checksum as reported by the WC. */
185251881Speter  const char *result_checksum;
186251881Speter
187299742Sdim  /* Changed properties (const char * -> svn_prop_t *) */
188299742Sdim  apr_hash_t *prop_changes;
189251881Speter
190251881Speter  /* URL to PUT the file at. */
191251881Speter  const char *url;
192251881Speter
193251881Speter} file_context_t;
194251881Speter
195251881Speter
196251881Speter/* Setup routines and handlers for various requests we'll invoke. */
197251881Speter
198251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
199251881Speterstatic svn_error_t *
200251881Spetercreate_checkout_body(serf_bucket_t **bkt,
201251881Speter                     void *baton,
202251881Speter                     serf_bucket_alloc_t *alloc,
203299742Sdim                     apr_pool_t *pool /* request pool */,
204299742Sdim                     apr_pool_t *scratch_pool)
205251881Speter{
206251881Speter  const char *activity_url = baton;
207251881Speter  serf_bucket_t *body_bkt;
208251881Speter
209251881Speter  body_bkt = serf_bucket_aggregate_create(alloc);
210251881Speter
211251881Speter  svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
212251881Speter  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
213251881Speter                                    "xmlns:D", "DAV:",
214299742Sdim                                    SVN_VA_NULL);
215299742Sdim  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set",
216299742Sdim                                    SVN_VA_NULL);
217299742Sdim  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href",
218299742Sdim                                    SVN_VA_NULL);
219251881Speter
220251881Speter  SVN_ERR_ASSERT(activity_url != NULL);
221251881Speter  svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
222251881Speter                                     activity_url,
223251881Speter                                     strlen(activity_url));
224251881Speter
225251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
226251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
227299742Sdim  svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
228299742Sdim                                     "D:apply-to-version", SVN_VA_NULL);
229251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
230251881Speter
231251881Speter  *bkt = body_bkt;
232251881Speter  return SVN_NO_ERROR;
233251881Speter}
234251881Speter
235251881Speter
236251881Speter/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
237251881Speter   given COMMIT_CTX. The resulting working resource will be returned in
238251881Speter   *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
239251881Speter   are performed in SCRATCH_POOL.
240251881Speter
241251881Speter   ### are these URLs actually repos relpath values? or fspath? or maybe
242251881Speter   ### the abspath portion of the full URL.
243251881Speter
244251881Speter   This function operates synchronously.
245251881Speter
246251881Speter   Strictly speaking, we could perform "all" of the CHECKOUT requests
247251881Speter   when the commit starts, and only block when we need a specific
248251881Speter   answer. Or, at a minimum, send off these individual requests async
249251881Speter   and block when we need the answer (eg PUT or PROPPATCH).
250251881Speter
251251881Speter   However: the investment to speed this up is not worthwhile, given
252251881Speter   that CHECKOUT (and the related round trip) is completely obviated
253251881Speter   in HTTPv2.
254251881Speter*/
255251881Speterstatic svn_error_t *
256251881Spetercheckout_node(const char **working_url,
257251881Speter              const commit_context_t *commit_ctx,
258251881Speter              const char *node_url,
259251881Speter              apr_pool_t *result_pool,
260251881Speter              apr_pool_t *scratch_pool)
261251881Speter{
262299742Sdim  svn_ra_serf__handler_t *handler;
263251881Speter  apr_status_t status;
264251881Speter  apr_uri_t uri;
265251881Speter
266251881Speter  /* HANDLER_POOL is the scratch pool since we don't need to remember
267251881Speter     anything from the handler. We just want the working resource.  */
268299742Sdim  handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
269251881Speter
270299742Sdim  handler->body_delegate = create_checkout_body;
271299742Sdim  handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
272299742Sdim  handler->body_type = "text/xml";
273251881Speter
274299742Sdim  handler->response_handler = svn_ra_serf__expect_empty_body;
275299742Sdim  handler->response_baton = handler;
276251881Speter
277299742Sdim  handler->method = "CHECKOUT";
278299742Sdim  handler->path = node_url;
279251881Speter
280299742Sdim  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
281251881Speter
282299742Sdim  if (handler->sline.code != 201)
283299742Sdim    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
284251881Speter
285299742Sdim  if (handler->location == NULL)
286251881Speter    return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
287251881Speter                            _("No Location header received"));
288251881Speter
289251881Speter  /* We only want the path portion of the Location header.
290251881Speter     (code.google.com sometimes returns an 'http:' scheme for an
291251881Speter     'https:' transaction ... we'll work around that by stripping the
292251881Speter     scheme, host, and port here and re-adding the correct ones
293251881Speter     later.  */
294299742Sdim  status = apr_uri_parse(scratch_pool, handler->location, &uri);
295251881Speter  if (status)
296251881Speter    return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
297251881Speter                            _("Error parsing Location header value"));
298251881Speter
299251881Speter  *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
300251881Speter
301251881Speter  return SVN_NO_ERROR;
302251881Speter}
303251881Speter
304251881Speter
305251881Speter/* This is a wrapper around checkout_node() (which see for
306251881Speter   documentation) which simply retries the CHECKOUT request when it
307251881Speter   fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
308251881Speter   server.
309251881Speter
310251881Speter   See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
311251881Speter   details.
312251881Speter*/
313251881Speterstatic svn_error_t *
314251881Speterretry_checkout_node(const char **working_url,
315251881Speter                    const commit_context_t *commit_ctx,
316251881Speter                    const char *node_url,
317251881Speter                    apr_pool_t *result_pool,
318251881Speter                    apr_pool_t *scratch_pool)
319251881Speter{
320251881Speter  svn_error_t *err = SVN_NO_ERROR;
321251881Speter  int retry_count = 5; /* Magic, arbitrary number. */
322251881Speter
323251881Speter  do
324251881Speter    {
325251881Speter      svn_error_clear(err);
326251881Speter
327251881Speter      err = checkout_node(working_url, commit_ctx, node_url,
328251881Speter                          result_pool, scratch_pool);
329251881Speter
330251881Speter      /* There's a small chance of a race condition here if Apache is
331251881Speter         experiencing heavy commit concurrency or if the network has
332251881Speter         long latency.  It's possible that the value of HEAD changed
333251881Speter         between the time we fetched the latest baseline and the time
334251881Speter         we try to CHECKOUT that baseline.  If that happens, Apache
335251881Speter         will throw us a BAD_BASELINE error (deltaV says you can only
336251881Speter         checkout the latest baseline).  We just ignore that specific
337251881Speter         error and retry a few times, asking for the latest baseline
338251881Speter         again. */
339251881Speter      if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
340299742Sdim        return svn_error_trace(err);
341251881Speter    }
342251881Speter  while (err && retry_count--);
343251881Speter
344299742Sdim  return svn_error_trace(err);
345251881Speter}
346251881Speter
347251881Speter
348251881Speterstatic svn_error_t *
349251881Spetercheckout_dir(dir_context_t *dir,
350251881Speter             apr_pool_t *scratch_pool)
351251881Speter{
352299742Sdim  dir_context_t *c_dir = dir;
353251881Speter  const char *checkout_url;
354251881Speter  const char **working;
355251881Speter
356251881Speter  if (dir->working_url)
357251881Speter    {
358251881Speter      return SVN_NO_ERROR;
359251881Speter    }
360251881Speter
361251881Speter  /* Is this directory or one of our parent dirs newly added?
362251881Speter   * If so, we're already implicitly checked out. */
363299742Sdim  while (c_dir)
364251881Speter    {
365299742Sdim      if (c_dir->added)
366251881Speter        {
367299742Sdim          /* Calculate the working_url by skipping the shared ancestor between
368299742Sdim           * the c_dir_parent->relpath and dir->relpath. This is safe since an
369262253Speter           * add is guaranteed to have a parent that is checked out. */
370299742Sdim          dir_context_t *c_dir_parent = c_dir->parent_dir;
371299742Sdim          const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath,
372262253Speter                                                          dir->relpath);
373262253Speter
374251881Speter          /* Implicitly checkout this dir now. */
375299742Sdim          SVN_ERR_ASSERT(c_dir_parent->working_url);
376251881Speter          dir->working_url = svn_path_url_add_component2(
377299742Sdim                                   c_dir_parent->working_url,
378262253Speter                                   relpath, dir->pool);
379251881Speter          return SVN_NO_ERROR;
380251881Speter        }
381299742Sdim      c_dir = c_dir->parent_dir;
382251881Speter    }
383251881Speter
384251881Speter  /* We could be called twice for the root: once to checkout the baseline;
385251881Speter   * once to checkout the directory itself if we need to do so.
386251881Speter   * Note: CHECKOUT_URL should live longer than HANDLER.
387251881Speter   */
388299742Sdim  if (!dir->parent_dir && !dir->commit_ctx->baseline_url)
389251881Speter    {
390299742Sdim      checkout_url = dir->commit_ctx->vcc_url;
391299742Sdim      working = &dir->commit_ctx->baseline_url;
392251881Speter    }
393251881Speter  else
394251881Speter    {
395251881Speter      checkout_url = dir->url;
396251881Speter      working = &dir->working_url;
397251881Speter    }
398251881Speter
399251881Speter  /* Checkout our directory into the activity URL now. */
400299742Sdim  return svn_error_trace(retry_checkout_node(working, dir->commit_ctx,
401299742Sdim                                             checkout_url,
402299742Sdim                                             dir->pool, scratch_pool));
403251881Speter}
404251881Speter
405251881Speter
406251881Speter/* Set *CHECKED_IN_URL to the appropriate DAV version url for
407251881Speter * RELPATH (relative to the root of SESSION).
408251881Speter *
409251881Speter * Try to find this version url in three ways:
410251881Speter * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
411251881Speter * version url from the working copy properties.
412251881Speter * Second, if the version url of the parent directory PARENT_VSN_URL is
413251881Speter * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
414251881Speter * RELPATH.
415251881Speter * Else, fetch the version url for the root of SESSION using CONN and
416251881Speter * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
417251881Speter * with RELPATH.
418251881Speter *
419251881Speter * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
420251881Speter * temporary allocation.
421251881Speter */
422251881Speterstatic svn_error_t *
423251881Speterget_version_url(const char **checked_in_url,
424251881Speter                svn_ra_serf__session_t *session,
425251881Speter                const char *relpath,
426251881Speter                svn_revnum_t base_revision,
427251881Speter                const char *parent_vsn_url,
428251881Speter                apr_pool_t *result_pool,
429251881Speter                apr_pool_t *scratch_pool)
430251881Speter{
431251881Speter  const char *root_checkout;
432251881Speter
433251881Speter  if (session->wc_callbacks->get_wc_prop)
434251881Speter    {
435251881Speter      const svn_string_t *current_version;
436251881Speter
437251881Speter      SVN_ERR(session->wc_callbacks->get_wc_prop(
438251881Speter                session->wc_callback_baton,
439251881Speter                relpath,
440251881Speter                SVN_RA_SERF__WC_CHECKED_IN_URL,
441251881Speter                &current_version, scratch_pool));
442251881Speter
443251881Speter      if (current_version)
444251881Speter        {
445251881Speter          *checked_in_url =
446251881Speter            svn_urlpath__canonicalize(current_version->data, result_pool);
447251881Speter          return SVN_NO_ERROR;
448251881Speter        }
449251881Speter    }
450251881Speter
451251881Speter  if (parent_vsn_url)
452251881Speter    {
453251881Speter      root_checkout = parent_vsn_url;
454251881Speter    }
455251881Speter  else
456251881Speter    {
457251881Speter      const char *propfind_url;
458251881Speter
459251881Speter      if (SVN_IS_VALID_REVNUM(base_revision))
460251881Speter        {
461251881Speter          /* mod_dav_svn can't handle the "Label:" header that
462251881Speter             svn_ra_serf__deliver_props() is going to try to use for
463251881Speter             this lookup, so we'll do things the hard(er) way, by
464251881Speter             looking up the version URL from a resource in the
465251881Speter             baseline collection. */
466251881Speter          SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
467251881Speter                                              NULL /* latest_revnum */,
468299742Sdim                                              session,
469251881Speter                                              NULL /* url */, base_revision,
470251881Speter                                              scratch_pool, scratch_pool));
471251881Speter        }
472251881Speter      else
473251881Speter        {
474251881Speter          propfind_url = session->session_url.path;
475251881Speter        }
476251881Speter
477299742Sdim      SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session,
478299742Sdim                                          propfind_url, base_revision,
479251881Speter                                          "checked-in",
480251881Speter                                          scratch_pool, scratch_pool));
481251881Speter      if (!root_checkout)
482251881Speter        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
483251881Speter                                 _("Path '%s' not present"),
484251881Speter                                 session->session_url.path);
485251881Speter
486251881Speter      root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
487251881Speter    }
488251881Speter
489251881Speter  *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
490251881Speter                                                result_pool);
491251881Speter
492251881Speter  return SVN_NO_ERROR;
493251881Speter}
494251881Speter
495251881Speterstatic svn_error_t *
496251881Spetercheckout_file(file_context_t *file,
497251881Speter              apr_pool_t *scratch_pool)
498251881Speter{
499251881Speter  dir_context_t *parent_dir = file->parent_dir;
500251881Speter  const char *checkout_url;
501251881Speter
502251881Speter  /* Is one of our parent dirs newly added?  If so, we're already
503251881Speter   * implicitly checked out.
504251881Speter   */
505251881Speter  while (parent_dir)
506251881Speter    {
507251881Speter      if (parent_dir->added)
508251881Speter        {
509251881Speter          /* Implicitly checkout this file now. */
510299742Sdim          SVN_ERR_ASSERT(parent_dir->working_url);
511251881Speter          file->working_url = svn_path_url_add_component2(
512251881Speter                                    parent_dir->working_url,
513251881Speter                                    svn_relpath_skip_ancestor(
514251881Speter                                      parent_dir->relpath, file->relpath),
515251881Speter                                    file->pool);
516251881Speter          return SVN_NO_ERROR;
517251881Speter        }
518251881Speter      parent_dir = parent_dir->parent_dir;
519251881Speter    }
520251881Speter
521251881Speter  SVN_ERR(get_version_url(&checkout_url,
522299742Sdim                          file->commit_ctx->session,
523251881Speter                          file->relpath, file->base_revision,
524251881Speter                          NULL, scratch_pool, scratch_pool));
525251881Speter
526251881Speter  /* Checkout our file into the activity URL now. */
527299742Sdim  return svn_error_trace(retry_checkout_node(&file->working_url,
528299742Sdim                                             file->commit_ctx, checkout_url,
529299742Sdim                                              file->pool, scratch_pool));
530251881Speter}
531251881Speter
532251881Speter/* Helper function for proppatch_walker() below. */
533251881Speterstatic svn_error_t *
534251881Speterget_encoding_and_cdata(const char **encoding_p,
535251881Speter                       const svn_string_t **encoded_value_p,
536251881Speter                       serf_bucket_alloc_t *alloc,
537251881Speter                       const svn_string_t *value,
538251881Speter                       apr_pool_t *result_pool,
539251881Speter                       apr_pool_t *scratch_pool)
540251881Speter{
541251881Speter  if (value == NULL)
542251881Speter    {
543251881Speter      *encoding_p = NULL;
544251881Speter      *encoded_value_p = NULL;
545251881Speter      return SVN_NO_ERROR;
546251881Speter    }
547251881Speter
548251881Speter  /* If a property is XML-safe, XML-encode it.  Else, base64-encode
549251881Speter     it. */
550251881Speter  if (svn_xml_is_xml_safe(value->data, value->len))
551251881Speter    {
552251881Speter      svn_stringbuf_t *xml_esc = NULL;
553251881Speter      svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
554251881Speter      *encoding_p = NULL;
555251881Speter      *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
556251881Speter    }
557251881Speter  else
558251881Speter    {
559251881Speter      *encoding_p = "base64";
560251881Speter      *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
561251881Speter    }
562251881Speter
563251881Speter  return SVN_NO_ERROR;
564251881Speter}
565251881Speter
566299742Sdim/* Helper for create_proppatch_body. Writes per property xml to body */
567251881Speterstatic svn_error_t *
568299742Sdimwrite_prop_xml(const proppatch_context_t *proppatch,
569299742Sdim               serf_bucket_t *body_bkt,
570299742Sdim               serf_bucket_alloc_t *alloc,
571299742Sdim               const svn_prop_t *prop,
572299742Sdim               apr_pool_t *result_pool,
573299742Sdim               apr_pool_t *scratch_pool)
574251881Speter{
575251881Speter  serf_bucket_t *cdata_bkt;
576251881Speter  const char *encoding;
577251881Speter  const svn_string_t *encoded_value;
578251881Speter  const char *prop_name;
579299742Sdim  const svn_prop_t *old_prop;
580251881Speter
581299742Sdim  SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value,
582299742Sdim                                 result_pool, scratch_pool));
583251881Speter  if (encoded_value)
584251881Speter    {
585251881Speter      cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
586251881Speter                                                encoded_value->len,
587251881Speter                                                alloc);
588251881Speter    }
589251881Speter  else
590251881Speter    {
591251881Speter      cdata_bkt = NULL;
592251881Speter    }
593251881Speter
594251881Speter  /* Use the namespace prefix instead of adding the xmlns attribute to support
595251881Speter     property names containing ':' */
596299742Sdim  if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
597299742Sdim    {
598299742Sdim      prop_name = apr_pstrcat(result_pool,
599299742Sdim                              "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1,
600299742Sdim                              SVN_VA_NULL);
601299742Sdim    }
602299742Sdim  else
603299742Sdim    {
604299742Sdim      prop_name = apr_pstrcat(result_pool,
605299742Sdim                              "C:", prop->name,
606299742Sdim                              SVN_VA_NULL);
607299742Sdim    }
608251881Speter
609251881Speter  if (cdata_bkt)
610251881Speter    svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
611251881Speter                                      "V:encoding", encoding,
612299742Sdim                                      SVN_VA_NULL);
613251881Speter  else
614251881Speter    svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
615251881Speter                                      "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
616299742Sdim                                      SVN_VA_NULL);
617251881Speter
618299742Sdim  old_prop = proppatch->old_props
619299742Sdim                          ? svn_hash_gets(proppatch->old_props, prop->name)
620299742Sdim                          : NULL;
621299742Sdim  if (old_prop)
622251881Speter    {
623251881Speter      const char *encoding2;
624251881Speter      const svn_string_t *encoded_value2;
625251881Speter      serf_bucket_t *cdata_bkt2;
626251881Speter
627251881Speter      SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
628299742Sdim                                     alloc, old_prop->value,
629299742Sdim                                     result_pool, scratch_pool));
630251881Speter
631251881Speter      if (encoded_value2)
632251881Speter        {
633251881Speter          cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
634251881Speter                                                     encoded_value2->len,
635251881Speter                                                     alloc);
636251881Speter        }
637251881Speter      else
638251881Speter        {
639251881Speter          cdata_bkt2 = NULL;
640251881Speter        }
641251881Speter
642251881Speter      if (cdata_bkt2)
643251881Speter        svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
644251881Speter                                          "V:" SVN_DAV__OLD_VALUE,
645251881Speter                                          "V:encoding", encoding2,
646299742Sdim                                          SVN_VA_NULL);
647251881Speter      else
648251881Speter        svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
649251881Speter                                          "V:" SVN_DAV__OLD_VALUE,
650251881Speter                                          "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
651299742Sdim                                          SVN_VA_NULL);
652251881Speter
653251881Speter      if (cdata_bkt2)
654251881Speter        serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
655251881Speter
656251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
657251881Speter                                         "V:" SVN_DAV__OLD_VALUE);
658251881Speter    }
659251881Speter  if (cdata_bkt)
660251881Speter    serf_bucket_aggregate_append(body_bkt, cdata_bkt);
661251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
662251881Speter
663251881Speter  return SVN_NO_ERROR;
664251881Speter}
665251881Speter
666251881Speter/* Possible add the lock-token "If:" precondition header to HEADERS if
667251881Speter   an examination of COMMIT_CTX and RELPATH indicates that this is the
668251881Speter   right thing to do.
669251881Speter
670251881Speter   Generally speaking, if the client provided a lock token for
671251881Speter   RELPATH, it's the right thing to do.  There is a notable instance
672251881Speter   where this is not the case, however.  If the file at RELPATH was
673251881Speter   explicitly deleted in this commit already, then mod_dav removed its
674251881Speter   lock token when it fielded the DELETE request, so we don't want to
675251881Speter   set the lock precondition again.  (See
676251881Speter   http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
677251881Speter*/
678251881Speterstatic svn_error_t *
679251881Spetermaybe_set_lock_token_header(serf_bucket_t *headers,
680251881Speter                            commit_context_t *commit_ctx,
681251881Speter                            const char *relpath,
682251881Speter                            apr_pool_t *pool)
683251881Speter{
684251881Speter  const char *token;
685251881Speter
686299742Sdim  if (! (*relpath && commit_ctx->lock_tokens))
687251881Speter    return SVN_NO_ERROR;
688251881Speter
689251881Speter  if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
690251881Speter    {
691251881Speter      token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
692251881Speter      if (token)
693251881Speter        {
694251881Speter          const char *token_header;
695251881Speter          const char *token_uri;
696251881Speter          apr_uri_t uri = commit_ctx->session->session_url;
697251881Speter
698251881Speter          /* Supplying the optional URI affects apache response when
699251881Speter             the lock is broken, see issue 4369.  When present any URI
700251881Speter             must be absolute (RFC 2518 9.4). */
701251881Speter          uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
702251881Speter                                                         pool);
703251881Speter          token_uri = apr_uri_unparse(pool, &uri, 0);
704251881Speter
705251881Speter          token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
706299742Sdim                                     SVN_VA_NULL);
707251881Speter          serf_bucket_headers_set(headers, "If", token_header);
708251881Speter        }
709251881Speter    }
710251881Speter
711251881Speter  return SVN_NO_ERROR;
712251881Speter}
713251881Speter
714251881Speterstatic svn_error_t *
715251881Spetersetup_proppatch_headers(serf_bucket_t *headers,
716251881Speter                        void *baton,
717299742Sdim                        apr_pool_t *pool /* request pool */,
718299742Sdim                        apr_pool_t *scratch_pool)
719251881Speter{
720251881Speter  proppatch_context_t *proppatch = baton;
721251881Speter
722251881Speter  if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
723251881Speter    {
724251881Speter      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
725251881Speter                              apr_psprintf(pool, "%ld",
726251881Speter                                           proppatch->base_revision));
727251881Speter    }
728251881Speter
729299742Sdim  if (proppatch->relpath && proppatch->commit_ctx)
730299742Sdim    SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx,
731299742Sdim                                        proppatch->relpath, pool));
732251881Speter
733251881Speter  return SVN_NO_ERROR;
734251881Speter}
735251881Speter
736251881Speter
737251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
738251881Speterstatic svn_error_t *
739251881Spetercreate_proppatch_body(serf_bucket_t **bkt,
740251881Speter                      void *baton,
741251881Speter                      serf_bucket_alloc_t *alloc,
742299742Sdim                      apr_pool_t *pool /* request pool */,
743251881Speter                      apr_pool_t *scratch_pool)
744251881Speter{
745299742Sdim  proppatch_context_t *ctx = baton;
746251881Speter  serf_bucket_t *body_bkt;
747299742Sdim  svn_boolean_t opened = FALSE;
748299742Sdim  apr_hash_index_t *hi;
749251881Speter
750251881Speter  body_bkt = serf_bucket_aggregate_create(alloc);
751251881Speter
752251881Speter  svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
753251881Speter  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
754251881Speter                                    "xmlns:D", "DAV:",
755251881Speter                                    "xmlns:V", SVN_DAV_PROP_NS_DAV,
756251881Speter                                    "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
757251881Speter                                    "xmlns:S", SVN_DAV_PROP_NS_SVN,
758299742Sdim                                    SVN_VA_NULL);
759251881Speter
760299742Sdim  /* First we write property SETs */
761299742Sdim  for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
762299742Sdim       hi;
763299742Sdim       hi = apr_hash_next(hi))
764251881Speter    {
765299742Sdim      svn_prop_t *prop = apr_hash_this_val(hi);
766251881Speter
767299742Sdim      if (prop->value
768299742Sdim          || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
769299742Sdim        {
770299742Sdim          if (!opened)
771299742Sdim            {
772299742Sdim              opened = TRUE;
773299742Sdim              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set",
774299742Sdim                                                SVN_VA_NULL);
775299742Sdim              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
776299742Sdim                                                SVN_VA_NULL);
777299742Sdim            }
778251881Speter
779299742Sdim          SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
780299742Sdim                                 pool, scratch_pool));
781299742Sdim        }
782299742Sdim    }
783299742Sdim
784299742Sdim  if (opened)
785299742Sdim    {
786251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
787251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
788251881Speter    }
789251881Speter
790299742Sdim  /* And then property REMOVEs */
791299742Sdim  opened = FALSE;
792299742Sdim
793299742Sdim  for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
794299742Sdim       hi;
795299742Sdim       hi = apr_hash_next(hi))
796251881Speter    {
797299742Sdim      svn_prop_t *prop = apr_hash_this_val(hi);
798251881Speter
799299742Sdim      if (!prop->value
800299742Sdim          && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
801299742Sdim        {
802299742Sdim          if (!opened)
803299742Sdim            {
804299742Sdim              opened = TRUE;
805299742Sdim              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove",
806299742Sdim                                                SVN_VA_NULL);
807299742Sdim              svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
808299742Sdim                                                SVN_VA_NULL);
809299742Sdim            }
810251881Speter
811299742Sdim          SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
812299742Sdim                                 pool, scratch_pool));
813299742Sdim        }
814251881Speter    }
815251881Speter
816299742Sdim  if (opened)
817251881Speter    {
818251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
819251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
820251881Speter    }
821251881Speter
822251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
823251881Speter
824251881Speter  *bkt = body_bkt;
825251881Speter  return SVN_NO_ERROR;
826251881Speter}
827251881Speter
828251881Speterstatic svn_error_t*
829299742Sdimproppatch_resource(svn_ra_serf__session_t *session,
830299742Sdim                   proppatch_context_t *proppatch,
831251881Speter                   apr_pool_t *pool)
832251881Speter{
833251881Speter  svn_ra_serf__handler_t *handler;
834299742Sdim  svn_error_t *err;
835251881Speter
836299742Sdim  handler = svn_ra_serf__create_handler(session, pool);
837299742Sdim
838251881Speter  handler->method = "PROPPATCH";
839251881Speter  handler->path = proppatch->path;
840251881Speter
841251881Speter  handler->header_delegate = setup_proppatch_headers;
842251881Speter  handler->header_delegate_baton = proppatch;
843251881Speter
844251881Speter  handler->body_delegate = create_proppatch_body;
845299742Sdim  handler->body_delegate_baton = proppatch;
846299742Sdim  handler->body_type = "text/xml";
847251881Speter
848251881Speter  handler->response_handler = svn_ra_serf__handle_multistatus_only;
849251881Speter  handler->response_baton = handler;
850251881Speter
851299742Sdim  err = svn_ra_serf__context_run_one(handler, pool);
852251881Speter
853299742Sdim  if (!err && handler->sline.code != 207)
854299742Sdim    err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
855299742Sdim
856299742Sdim  /* Use specific error code for property handling errors.
857299742Sdim     Use loop to provide the right result with tracing */
858299742Sdim  if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
859251881Speter    {
860299742Sdim      svn_error_t *e = err;
861299742Sdim
862299742Sdim      while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
863299742Sdim        {
864299742Sdim          e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED;
865299742Sdim          e = e->child;
866299742Sdim        }
867251881Speter    }
868251881Speter
869299742Sdim  return svn_error_trace(err);
870251881Speter}
871251881Speter
872251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
873251881Speterstatic svn_error_t *
874251881Spetercreate_put_body(serf_bucket_t **body_bkt,
875251881Speter                void *baton,
876251881Speter                serf_bucket_alloc_t *alloc,
877299742Sdim                apr_pool_t *pool /* request pool */,
878299742Sdim                apr_pool_t *scratch_pool)
879251881Speter{
880251881Speter  file_context_t *ctx = baton;
881251881Speter  apr_off_t offset;
882251881Speter
883251881Speter  /* We need to flush the file, make it unbuffered (so that it can be
884251881Speter   * zero-copied via mmap), and reset the position before attempting to
885251881Speter   * deliver the file.
886251881Speter   *
887251881Speter   * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
888251881Speter   * and zero-copy the PUT body.  However, on older APR versions, we can't
889251881Speter   * check the buffer status; but serf will fall through and create a file
890251881Speter   * bucket for us on the buffered svndiff handle.
891251881Speter   */
892299742Sdim  SVN_ERR(svn_io_file_flush(ctx->svndiff, pool));
893251881Speter  apr_file_buffer_set(ctx->svndiff, NULL, 0);
894251881Speter  offset = 0;
895299742Sdim  SVN_ERR(svn_io_file_seek(ctx->svndiff, APR_SET, &offset, pool));
896251881Speter
897251881Speter  *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
898251881Speter  return SVN_NO_ERROR;
899251881Speter}
900251881Speter
901251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
902251881Speterstatic svn_error_t *
903251881Spetercreate_empty_put_body(serf_bucket_t **body_bkt,
904251881Speter                      void *baton,
905251881Speter                      serf_bucket_alloc_t *alloc,
906299742Sdim                      apr_pool_t *pool /* request pool */,
907299742Sdim                      apr_pool_t *scratch_pool)
908251881Speter{
909251881Speter  *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
910251881Speter  return SVN_NO_ERROR;
911251881Speter}
912251881Speter
913251881Speterstatic svn_error_t *
914251881Spetersetup_put_headers(serf_bucket_t *headers,
915251881Speter                  void *baton,
916299742Sdim                  apr_pool_t *pool /* request pool */,
917299742Sdim                  apr_pool_t *scratch_pool)
918251881Speter{
919251881Speter  file_context_t *ctx = baton;
920251881Speter
921251881Speter  if (SVN_IS_VALID_REVNUM(ctx->base_revision))
922251881Speter    {
923251881Speter      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
924251881Speter                              apr_psprintf(pool, "%ld", ctx->base_revision));
925251881Speter    }
926251881Speter
927251881Speter  if (ctx->base_checksum)
928251881Speter    {
929251881Speter      serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
930251881Speter                              ctx->base_checksum);
931251881Speter    }
932251881Speter
933251881Speter  if (ctx->result_checksum)
934251881Speter    {
935251881Speter      serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
936251881Speter                              ctx->result_checksum);
937251881Speter    }
938251881Speter
939299742Sdim  SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx,
940251881Speter                                      ctx->relpath, pool));
941251881Speter
942251881Speter  return APR_SUCCESS;
943251881Speter}
944251881Speter
945251881Speterstatic svn_error_t *
946251881Spetersetup_copy_file_headers(serf_bucket_t *headers,
947251881Speter                        void *baton,
948299742Sdim                        apr_pool_t *pool /* request pool */,
949299742Sdim                        apr_pool_t *scratch_pool)
950251881Speter{
951251881Speter  file_context_t *file = baton;
952251881Speter  apr_uri_t uri;
953251881Speter  const char *absolute_uri;
954251881Speter
955251881Speter  /* The Dest URI must be absolute.  Bummer. */
956299742Sdim  uri = file->commit_ctx->session->session_url;
957251881Speter  uri.path = (char*)file->url;
958251881Speter  absolute_uri = apr_uri_unparse(pool, &uri, 0);
959251881Speter
960251881Speter  serf_bucket_headers_set(headers, "Destination", absolute_uri);
961251881Speter
962299742Sdim  serf_bucket_headers_setn(headers, "Overwrite", "F");
963251881Speter
964251881Speter  return SVN_NO_ERROR;
965251881Speter}
966251881Speter
967251881Speterstatic svn_error_t *
968269847Spetersetup_if_header_recursive(svn_boolean_t *added,
969269847Speter                          serf_bucket_t *headers,
970269847Speter                          commit_context_t *commit_ctx,
971269847Speter                          const char *rq_relpath,
972269847Speter                          apr_pool_t *pool)
973269847Speter{
974269847Speter  svn_stringbuf_t *sb = NULL;
975269847Speter  apr_hash_index_t *hi;
976269847Speter  apr_pool_t *iterpool = NULL;
977269847Speter
978269847Speter  if (!commit_ctx->lock_tokens)
979269847Speter    {
980269847Speter      *added = FALSE;
981269847Speter      return SVN_NO_ERROR;
982269847Speter    }
983269847Speter
984269847Speter  /* We try to create a directory, so within the Subversion world that
985269847Speter     would imply that there is nothing here, but mod_dav_svn still sees
986269847Speter     locks on the old nodes here as in DAV it is perfectly legal to lock
987269847Speter     something that is not there...
988269847Speter
989269847Speter     Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
990269847Speter     the locks we know of with the request */
991269847Speter
992269847Speter  for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
993269847Speter       hi;
994269847Speter       hi = apr_hash_next(hi))
995269847Speter    {
996299742Sdim      const char *relpath = apr_hash_this_key(hi);
997269847Speter      apr_uri_t uri;
998269847Speter
999269847Speter      if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
1000269847Speter        continue;
1001269847Speter      else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
1002269847Speter        {
1003269847Speter          /* When a path is already explicit deleted then its lock
1004269847Speter             will be removed by mod_dav. But mod_dav doesn't remove
1005269847Speter             locks on descendants */
1006269847Speter          continue;
1007269847Speter        }
1008269847Speter
1009269847Speter      if (!iterpool)
1010269847Speter        iterpool = svn_pool_create(pool);
1011269847Speter      else
1012269847Speter        svn_pool_clear(iterpool);
1013269847Speter
1014269847Speter      if (sb == NULL)
1015269847Speter        sb = svn_stringbuf_create("", pool);
1016269847Speter      else
1017269847Speter        svn_stringbuf_appendbyte(sb, ' ');
1018269847Speter
1019269847Speter      uri = commit_ctx->session->session_url;
1020269847Speter      uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
1021269847Speter                                                    iterpool);
1022269847Speter
1023269847Speter      svn_stringbuf_appendbyte(sb, '<');
1024269847Speter      svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1025269847Speter      svn_stringbuf_appendcstr(sb, "> (<");
1026299742Sdim      svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi));
1027269847Speter      svn_stringbuf_appendcstr(sb, ">)");
1028269847Speter    }
1029269847Speter
1030269847Speter  if (iterpool)
1031269847Speter    svn_pool_destroy(iterpool);
1032269847Speter
1033269847Speter  if (sb)
1034269847Speter    {
1035269847Speter      serf_bucket_headers_set(headers, "If", sb->data);
1036269847Speter      *added = TRUE;
1037269847Speter    }
1038269847Speter  else
1039269847Speter    *added = FALSE;
1040269847Speter
1041269847Speter  return SVN_NO_ERROR;
1042269847Speter}
1043269847Speter
1044269847Speterstatic svn_error_t *
1045269847Spetersetup_add_dir_common_headers(serf_bucket_t *headers,
1046269847Speter                             void *baton,
1047299742Sdim                             apr_pool_t *pool /* request pool */,
1048299742Sdim                             apr_pool_t *scratch_pool)
1049269847Speter{
1050269847Speter  dir_context_t *dir = baton;
1051269847Speter  svn_boolean_t added;
1052269847Speter
1053269847Speter  return svn_error_trace(
1054299742Sdim      setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath,
1055269847Speter                                pool));
1056269847Speter}
1057269847Speter
1058269847Speterstatic svn_error_t *
1059251881Spetersetup_copy_dir_headers(serf_bucket_t *headers,
1060251881Speter                       void *baton,
1061299742Sdim                       apr_pool_t *pool /* request pool */,
1062299742Sdim                       apr_pool_t *scratch_pool)
1063251881Speter{
1064251881Speter  dir_context_t *dir = baton;
1065251881Speter  apr_uri_t uri;
1066251881Speter  const char *absolute_uri;
1067251881Speter
1068251881Speter  /* The Dest URI must be absolute.  Bummer. */
1069299742Sdim  uri = dir->commit_ctx->session->session_url;
1070251881Speter
1071299742Sdim  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1072251881Speter    {
1073251881Speter      uri.path = (char *)dir->url;
1074251881Speter    }
1075251881Speter  else
1076251881Speter    {
1077251881Speter      uri.path = (char *)svn_path_url_add_component2(
1078251881Speter                                    dir->parent_dir->working_url,
1079251881Speter                                    dir->name, pool);
1080251881Speter    }
1081251881Speter  absolute_uri = apr_uri_unparse(pool, &uri, 0);
1082251881Speter
1083251881Speter  serf_bucket_headers_set(headers, "Destination", absolute_uri);
1084251881Speter
1085251881Speter  serf_bucket_headers_setn(headers, "Depth", "infinity");
1086299742Sdim  serf_bucket_headers_setn(headers, "Overwrite", "F");
1087251881Speter
1088251881Speter  /* Implicitly checkout this dir now. */
1089251881Speter  dir->working_url = apr_pstrdup(dir->pool, uri.path);
1090251881Speter
1091299742Sdim  return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool,
1092299742Sdim                                                      scratch_pool));
1093251881Speter}
1094251881Speter
1095251881Speterstatic svn_error_t *
1096251881Spetersetup_delete_headers(serf_bucket_t *headers,
1097251881Speter                     void *baton,
1098299742Sdim                     apr_pool_t *pool /* request pool */,
1099299742Sdim                     apr_pool_t *scratch_pool)
1100251881Speter{
1101269847Speter  delete_context_t *del = baton;
1102269847Speter  svn_boolean_t added;
1103251881Speter
1104251881Speter  serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1105269847Speter                          apr_ltoa(pool, del->revision));
1106251881Speter
1107299742Sdim  if (! del->non_recursive_if)
1108299742Sdim    SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx,
1109299742Sdim                                      del->relpath, pool));
1110299742Sdim  else
1111299742Sdim    {
1112299742Sdim      SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx,
1113299742Sdim                                          del->relpath, pool));
1114299742Sdim      added = TRUE;
1115299742Sdim    }
1116251881Speter
1117299742Sdim  if (added && del->commit_ctx->keep_locks)
1118269847Speter    serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1119269847Speter                                      SVN_DAV_OPTION_KEEP_LOCKS);
1120251881Speter
1121251881Speter  return SVN_NO_ERROR;
1122251881Speter}
1123251881Speter
1124251881Speter/* POST against 'me' resource handlers. */
1125251881Speter
1126251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
1127251881Speterstatic svn_error_t *
1128251881Spetercreate_txn_post_body(serf_bucket_t **body_bkt,
1129251881Speter                     void *baton,
1130251881Speter                     serf_bucket_alloc_t *alloc,
1131299742Sdim                     apr_pool_t *pool /* request pool */,
1132299742Sdim                     apr_pool_t *scratch_pool)
1133251881Speter{
1134251881Speter  apr_hash_t *revprops = baton;
1135251881Speter  svn_skel_t *request_skel;
1136251881Speter  svn_stringbuf_t *skel_str;
1137251881Speter
1138251881Speter  request_skel = svn_skel__make_empty_list(pool);
1139251881Speter  if (revprops)
1140251881Speter    {
1141251881Speter      svn_skel_t *proplist_skel;
1142251881Speter
1143251881Speter      SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1144251881Speter      svn_skel__prepend(proplist_skel, request_skel);
1145251881Speter      svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1146251881Speter      skel_str = svn_skel__unparse(request_skel, pool);
1147251881Speter      *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1148251881Speter    }
1149251881Speter  else
1150251881Speter    {
1151251881Speter      *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1152251881Speter    }
1153251881Speter
1154251881Speter  return SVN_NO_ERROR;
1155251881Speter}
1156251881Speter
1157251881Speter/* Implements svn_ra_serf__request_header_delegate_t */
1158251881Speterstatic svn_error_t *
1159251881Spetersetup_post_headers(serf_bucket_t *headers,
1160251881Speter                   void *baton,
1161299742Sdim                   apr_pool_t *pool /* request pool */,
1162299742Sdim                   apr_pool_t *scratch_pool)
1163251881Speter{
1164251881Speter#ifdef SVN_DAV_SEND_VTXN_NAME
1165251881Speter  /* Enable this to exercise the VTXN-NAME code based on a client
1166251881Speter     supplied transaction name. */
1167251881Speter  serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1168251881Speter                          svn_uuid_generate(pool));
1169251881Speter#endif
1170251881Speter
1171251881Speter  return SVN_NO_ERROR;
1172251881Speter}
1173251881Speter
1174251881Speter
1175251881Speter/* Handler baton for POST request. */
1176251881Spetertypedef struct post_response_ctx_t
1177251881Speter{
1178251881Speter  svn_ra_serf__handler_t *handler;
1179251881Speter  commit_context_t *commit_ctx;
1180251881Speter} post_response_ctx_t;
1181251881Speter
1182251881Speter
1183251881Speter/* This implements serf_bucket_headers_do_callback_fn_t.   */
1184251881Speterstatic int
1185251881Speterpost_headers_iterator_callback(void *baton,
1186251881Speter                               const char *key,
1187251881Speter                               const char *val)
1188251881Speter{
1189251881Speter  post_response_ctx_t *prc = baton;
1190251881Speter  commit_context_t *prc_cc = prc->commit_ctx;
1191251881Speter  svn_ra_serf__session_t *sess = prc_cc->session;
1192251881Speter
1193251881Speter  /* If we provided a UUID to the POST request, we should get back
1194251881Speter     from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1195251881Speter     expect the SVN_DAV_TXN_NAME_HEADER.  We certainly don't expect to
1196251881Speter     see both. */
1197251881Speter
1198251881Speter  if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1199251881Speter    {
1200251881Speter      /* Build out txn and txn-root URLs using the txn name we're
1201251881Speter         given, and store the whole lot of it in the commit context.  */
1202251881Speter      prc_cc->txn_url =
1203251881Speter        svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1204251881Speter      prc_cc->txn_root_url =
1205251881Speter        svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1206251881Speter    }
1207251881Speter
1208251881Speter  if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1209251881Speter    {
1210251881Speter      /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1211251881Speter         given, and store the whole lot of it in the commit context.  */
1212251881Speter      prc_cc->txn_url =
1213251881Speter        svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1214251881Speter      prc_cc->txn_root_url =
1215251881Speter        svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1216251881Speter    }
1217251881Speter
1218251881Speter  return 0;
1219251881Speter}
1220251881Speter
1221251881Speter
1222251881Speter/* A custom serf_response_handler_t which is mostly a wrapper around
1223251881Speter   svn_ra_serf__expect_empty_body -- it just notices POST response
1224251881Speter   headers, too.
1225251881Speter
1226251881Speter   Implements svn_ra_serf__response_handler_t */
1227251881Speterstatic svn_error_t *
1228251881Speterpost_response_handler(serf_request_t *request,
1229251881Speter                      serf_bucket_t *response,
1230251881Speter                      void *baton,
1231251881Speter                      apr_pool_t *scratch_pool)
1232251881Speter{
1233251881Speter  post_response_ctx_t *prc = baton;
1234251881Speter  serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1235251881Speter
1236251881Speter  /* Then see which ones we can discover. */
1237251881Speter  serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1238251881Speter
1239251881Speter  /* Execute the 'real' response handler to XML-parse the repsonse body. */
1240251881Speter  return svn_ra_serf__expect_empty_body(request, response,
1241251881Speter                                        prc->handler, scratch_pool);
1242251881Speter}
1243251881Speter
1244251881Speter
1245251881Speter
1246251881Speter/* Commit baton callbacks */
1247251881Speter
1248251881Speterstatic svn_error_t *
1249251881Speteropen_root(void *edit_baton,
1250251881Speter          svn_revnum_t base_revision,
1251251881Speter          apr_pool_t *dir_pool,
1252251881Speter          void **root_baton)
1253251881Speter{
1254299742Sdim  commit_context_t *commit_ctx = edit_baton;
1255251881Speter  svn_ra_serf__handler_t *handler;
1256251881Speter  proppatch_context_t *proppatch_ctx;
1257251881Speter  dir_context_t *dir;
1258251881Speter  apr_hash_index_t *hi;
1259251881Speter  const char *proppatch_target = NULL;
1260299742Sdim  apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
1261251881Speter
1262299742Sdim  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
1263251881Speter    {
1264251881Speter      post_response_ctx_t *prc;
1265251881Speter      const char *rel_path;
1266251881Speter      svn_boolean_t post_with_revprops
1267299742Sdim        = (NULL != svn_hash_gets(commit_ctx->session->supported_posts,
1268251881Speter                                 "create-txn-with-props"));
1269251881Speter
1270251881Speter      /* Create our activity URL now on the server. */
1271299742Sdim      handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1272299742Sdim
1273251881Speter      handler->method = "POST";
1274251881Speter      handler->body_type = SVN_SKEL_MIME_TYPE;
1275251881Speter      handler->body_delegate = create_txn_post_body;
1276251881Speter      handler->body_delegate_baton =
1277299742Sdim          post_with_revprops ? commit_ctx->revprop_table : NULL;
1278251881Speter      handler->header_delegate = setup_post_headers;
1279251881Speter      handler->header_delegate_baton = NULL;
1280299742Sdim      handler->path = commit_ctx->session->me_resource;
1281251881Speter
1282299742Sdim      prc = apr_pcalloc(scratch_pool, sizeof(*prc));
1283251881Speter      prc->handler = handler;
1284299742Sdim      prc->commit_ctx = commit_ctx;
1285251881Speter
1286251881Speter      handler->response_handler = post_response_handler;
1287251881Speter      handler->response_baton = prc;
1288251881Speter
1289299742Sdim      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1290251881Speter
1291251881Speter      if (handler->sline.code != 201)
1292299742Sdim        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1293251881Speter
1294299742Sdim      if (! (commit_ctx->txn_root_url && commit_ctx->txn_url))
1295251881Speter        {
1296251881Speter          return svn_error_createf(
1297251881Speter            SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1298251881Speter            _("POST request did not return transaction information"));
1299251881Speter        }
1300251881Speter
1301251881Speter      /* Fixup the txn_root_url to point to the anchor of the commit. */
1302299742Sdim      SVN_ERR(svn_ra_serf__get_relative_path(
1303299742Sdim                                        &rel_path,
1304299742Sdim                                        commit_ctx->session->session_url.path,
1305299742Sdim                                        commit_ctx->session,
1306299742Sdim                                        scratch_pool));
1307299742Sdim      commit_ctx->txn_root_url = svn_path_url_add_component2(
1308299742Sdim                                        commit_ctx->txn_root_url,
1309299742Sdim                                        rel_path, commit_ctx->pool);
1310251881Speter
1311251881Speter      /* Build our directory baton. */
1312251881Speter      dir = apr_pcalloc(dir_pool, sizeof(*dir));
1313251881Speter      dir->pool = dir_pool;
1314299742Sdim      dir->commit_ctx = commit_ctx;
1315251881Speter      dir->base_revision = base_revision;
1316251881Speter      dir->relpath = "";
1317251881Speter      dir->name = "";
1318299742Sdim      dir->prop_changes = apr_hash_make(dir->pool);
1319299742Sdim      dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url);
1320251881Speter
1321251881Speter      /* If we included our revprops in the POST, we need not
1322251881Speter         PROPPATCH them. */
1323299742Sdim      proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url;
1324251881Speter    }
1325251881Speter  else
1326251881Speter    {
1327299742Sdim      const char *activity_str = commit_ctx->session->activity_collection_url;
1328251881Speter
1329251881Speter      if (!activity_str)
1330299742Sdim        SVN_ERR(svn_ra_serf__v1_get_activity_collection(
1331299742Sdim                                    &activity_str,
1332299742Sdim                                    commit_ctx->session,
1333299742Sdim                                    scratch_pool, scratch_pool));
1334251881Speter
1335299742Sdim      commit_ctx->activity_url = svn_path_url_add_component2(
1336299742Sdim                                    activity_str,
1337299742Sdim                                    svn_uuid_generate(scratch_pool),
1338299742Sdim                                    commit_ctx->pool);
1339251881Speter
1340299742Sdim      /* Create our activity URL now on the server. */
1341299742Sdim      handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1342251881Speter
1343251881Speter      handler->method = "MKACTIVITY";
1344299742Sdim      handler->path = commit_ctx->activity_url;
1345251881Speter
1346251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
1347251881Speter      handler->response_baton = handler;
1348251881Speter
1349299742Sdim      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1350251881Speter
1351251881Speter      if (handler->sline.code != 201)
1352299742Sdim        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1353251881Speter
1354251881Speter      /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1355299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url),
1356299742Sdim                                        commit_ctx->session, scratch_pool));
1357251881Speter
1358251881Speter
1359251881Speter      /* Build our directory baton. */
1360251881Speter      dir = apr_pcalloc(dir_pool, sizeof(*dir));
1361251881Speter      dir->pool = dir_pool;
1362299742Sdim      dir->commit_ctx = commit_ctx;
1363251881Speter      dir->base_revision = base_revision;
1364251881Speter      dir->relpath = "";
1365251881Speter      dir->name = "";
1366299742Sdim      dir->prop_changes = apr_hash_make(dir->pool);
1367251881Speter
1368299742Sdim      SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session,
1369251881Speter                              dir->relpath,
1370299742Sdim                              dir->base_revision, commit_ctx->checked_in_url,
1371299742Sdim                              dir->pool, scratch_pool));
1372299742Sdim      commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url);
1373251881Speter
1374251881Speter      /* Checkout our root dir */
1375299742Sdim      SVN_ERR(checkout_dir(dir, scratch_pool));
1376251881Speter
1377299742Sdim      proppatch_target = commit_ctx->baseline_url;
1378251881Speter    }
1379251881Speter
1380251881Speter  /* Unless this is NULL -- which means we don't need to PROPPATCH the
1381251881Speter     transaction with our revprops -- then, you know, PROPPATCH the
1382251881Speter     transaction with our revprops.  */
1383251881Speter  if (proppatch_target)
1384251881Speter    {
1385299742Sdim      proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx));
1386299742Sdim      proppatch_ctx->pool = scratch_pool;
1387299742Sdim      proppatch_ctx->commit_ctx = NULL; /* No lock info */
1388251881Speter      proppatch_ctx->path = proppatch_target;
1389299742Sdim      proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool);
1390251881Speter      proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1391251881Speter
1392299742Sdim      for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table);
1393299742Sdim           hi;
1394251881Speter           hi = apr_hash_next(hi))
1395251881Speter        {
1396299742Sdim          svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop));
1397251881Speter
1398299742Sdim          prop->name = apr_hash_this_key(hi);
1399299742Sdim          prop->value = apr_hash_this_val(hi);
1400251881Speter
1401299742Sdim          svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
1402251881Speter        }
1403251881Speter
1404299742Sdim      SVN_ERR(proppatch_resource(commit_ctx->session,
1405299742Sdim                                 proppatch_ctx, scratch_pool));
1406251881Speter    }
1407251881Speter
1408299742Sdim  svn_pool_destroy(scratch_pool);
1409299742Sdim
1410251881Speter  *root_baton = dir;
1411251881Speter
1412251881Speter  return SVN_NO_ERROR;
1413251881Speter}
1414251881Speter
1415299742Sdim/* Implements svn_ra_serf__request_body_delegate_t */
1416251881Speterstatic svn_error_t *
1417299742Sdimcreate_delete_body(serf_bucket_t **body_bkt,
1418299742Sdim                   void *baton,
1419299742Sdim                   serf_bucket_alloc_t *alloc,
1420299742Sdim                   apr_pool_t *pool /* request pool */,
1421299742Sdim                   apr_pool_t *scratch_pool)
1422299742Sdim{
1423299742Sdim  delete_context_t *ctx = baton;
1424299742Sdim  serf_bucket_t *body;
1425299742Sdim
1426299742Sdim  body = serf_bucket_aggregate_create(alloc);
1427299742Sdim
1428299742Sdim  svn_ra_serf__add_xml_header_buckets(body, alloc);
1429299742Sdim
1430299742Sdim  svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens,
1431299742Sdim                                     ctx->relpath, body, alloc, pool);
1432299742Sdim
1433299742Sdim  *body_bkt = body;
1434299742Sdim  return SVN_NO_ERROR;
1435299742Sdim}
1436299742Sdim
1437299742Sdimstatic svn_error_t *
1438251881Speterdelete_entry(const char *path,
1439251881Speter             svn_revnum_t revision,
1440251881Speter             void *parent_baton,
1441251881Speter             apr_pool_t *pool)
1442251881Speter{
1443251881Speter  dir_context_t *dir = parent_baton;
1444251881Speter  delete_context_t *delete_ctx;
1445251881Speter  svn_ra_serf__handler_t *handler;
1446251881Speter  const char *delete_target;
1447251881Speter
1448299742Sdim  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1449251881Speter    {
1450299742Sdim      delete_target = svn_path_url_add_component2(
1451299742Sdim                                    dir->commit_ctx->txn_root_url,
1452299742Sdim                                    path, dir->pool);
1453251881Speter    }
1454251881Speter  else
1455251881Speter    {
1456251881Speter      /* Ensure our directory has been checked out */
1457251881Speter      SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1458251881Speter      delete_target = svn_path_url_add_component2(dir->working_url,
1459251881Speter                                                  svn_relpath_basename(path,
1460251881Speter                                                                       NULL),
1461251881Speter                                                  pool);
1462251881Speter    }
1463251881Speter
1464251881Speter  /* DELETE our entry */
1465251881Speter  delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1466269847Speter  delete_ctx->relpath = apr_pstrdup(pool, path);
1467251881Speter  delete_ctx->revision = revision;
1468299742Sdim  delete_ctx->commit_ctx = dir->commit_ctx;
1469251881Speter
1470299742Sdim  handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1471251881Speter
1472251881Speter  handler->response_handler = svn_ra_serf__expect_empty_body;
1473251881Speter  handler->response_baton = handler;
1474251881Speter
1475251881Speter  handler->header_delegate = setup_delete_headers;
1476251881Speter  handler->header_delegate_baton = delete_ctx;
1477251881Speter
1478251881Speter  handler->method = "DELETE";
1479251881Speter  handler->path = delete_target;
1480299742Sdim  handler->no_fail_on_http_failure_status = TRUE;
1481251881Speter
1482269847Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1483251881Speter
1484299742Sdim  if (handler->sline.code == 400)
1485251881Speter    {
1486299742Sdim      /* Try again with non-standard body to overcome Apache Httpd
1487299742Sdim         header limit */
1488299742Sdim      delete_ctx->non_recursive_if = TRUE;
1489299742Sdim
1490299742Sdim      handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1491299742Sdim
1492299742Sdim      handler->response_handler = svn_ra_serf__expect_empty_body;
1493299742Sdim      handler->response_baton = handler;
1494299742Sdim
1495299742Sdim      handler->header_delegate = setup_delete_headers;
1496299742Sdim      handler->header_delegate_baton = delete_ctx;
1497299742Sdim
1498299742Sdim      handler->method = "DELETE";
1499299742Sdim      handler->path = delete_target;
1500299742Sdim
1501299742Sdim      handler->body_type = "text/xml";
1502299742Sdim      handler->body_delegate = create_delete_body;
1503299742Sdim      handler->body_delegate_baton = delete_ctx;
1504299742Sdim
1505299742Sdim      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1506251881Speter    }
1507251881Speter
1508299742Sdim  if (handler->server_error)
1509299742Sdim    return svn_ra_serf__server_error_create(handler, pool);
1510251881Speter
1511299742Sdim  /* 204 No Content: item successfully deleted */
1512299742Sdim  if (handler->sline.code != 204)
1513299742Sdim    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1514299742Sdim
1515299742Sdim  svn_hash_sets(dir->commit_ctx->deleted_entries,
1516299742Sdim                apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
1517299742Sdim
1518251881Speter  return SVN_NO_ERROR;
1519251881Speter}
1520251881Speter
1521251881Speterstatic svn_error_t *
1522251881Speteradd_directory(const char *path,
1523251881Speter              void *parent_baton,
1524251881Speter              const char *copyfrom_path,
1525251881Speter              svn_revnum_t copyfrom_revision,
1526251881Speter              apr_pool_t *dir_pool,
1527251881Speter              void **child_baton)
1528251881Speter{
1529251881Speter  dir_context_t *parent = parent_baton;
1530251881Speter  dir_context_t *dir;
1531251881Speter  svn_ra_serf__handler_t *handler;
1532251881Speter  apr_status_t status;
1533251881Speter  const char *mkcol_target;
1534251881Speter
1535251881Speter  dir = apr_pcalloc(dir_pool, sizeof(*dir));
1536251881Speter
1537251881Speter  dir->pool = dir_pool;
1538251881Speter  dir->parent_dir = parent;
1539299742Sdim  dir->commit_ctx = parent->commit_ctx;
1540251881Speter  dir->added = TRUE;
1541251881Speter  dir->base_revision = SVN_INVALID_REVNUM;
1542251881Speter  dir->copy_revision = copyfrom_revision;
1543262253Speter  dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1544251881Speter  dir->relpath = apr_pstrdup(dir->pool, path);
1545251881Speter  dir->name = svn_relpath_basename(dir->relpath, NULL);
1546299742Sdim  dir->prop_changes = apr_hash_make(dir->pool);
1547251881Speter
1548299742Sdim  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1549251881Speter    {
1550299742Sdim      dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1551251881Speter                                             path, dir->pool);
1552251881Speter      mkcol_target = dir->url;
1553251881Speter    }
1554251881Speter  else
1555251881Speter    {
1556251881Speter      /* Ensure our parent is checked out. */
1557251881Speter      SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1558251881Speter
1559299742Sdim      dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
1560251881Speter                                             dir->name, dir->pool);
1561251881Speter      mkcol_target = svn_path_url_add_component2(
1562251881Speter                               parent->working_url,
1563251881Speter                               dir->name, dir->pool);
1564251881Speter    }
1565251881Speter
1566299742Sdim  handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
1567251881Speter
1568251881Speter  handler->response_handler = svn_ra_serf__expect_empty_body;
1569251881Speter  handler->response_baton = handler;
1570251881Speter  if (!dir->copy_path)
1571251881Speter    {
1572251881Speter      handler->method = "MKCOL";
1573251881Speter      handler->path = mkcol_target;
1574269847Speter
1575269847Speter      handler->header_delegate = setup_add_dir_common_headers;
1576269847Speter      handler->header_delegate_baton = dir;
1577251881Speter    }
1578251881Speter  else
1579251881Speter    {
1580251881Speter      apr_uri_t uri;
1581251881Speter      const char *req_url;
1582251881Speter
1583251881Speter      status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1584251881Speter      if (status)
1585251881Speter        {
1586251881Speter          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1587251881Speter                                   _("Unable to parse URL '%s'"),
1588251881Speter                                   dir->copy_path);
1589251881Speter        }
1590251881Speter
1591251881Speter      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1592299742Sdim                                          dir->commit_ctx->session,
1593251881Speter                                          uri.path, dir->copy_revision,
1594251881Speter                                          dir_pool, dir_pool));
1595251881Speter
1596251881Speter      handler->method = "COPY";
1597251881Speter      handler->path = req_url;
1598251881Speter
1599251881Speter      handler->header_delegate = setup_copy_dir_headers;
1600251881Speter      handler->header_delegate_baton = dir;
1601251881Speter    }
1602299742Sdim  /* We have the same problem as with DELETE here: if there are too many
1603299742Sdim     locks, the request fails. But in this case there is no way to retry
1604299742Sdim     with a non-standard request. #### How to fix? */
1605251881Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1606251881Speter
1607299742Sdim  if (handler->sline.code != 201)
1608299742Sdim    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1609251881Speter
1610251881Speter  *child_baton = dir;
1611251881Speter
1612251881Speter  return SVN_NO_ERROR;
1613251881Speter}
1614251881Speter
1615251881Speterstatic svn_error_t *
1616251881Speteropen_directory(const char *path,
1617251881Speter               void *parent_baton,
1618251881Speter               svn_revnum_t base_revision,
1619251881Speter               apr_pool_t *dir_pool,
1620251881Speter               void **child_baton)
1621251881Speter{
1622251881Speter  dir_context_t *parent = parent_baton;
1623251881Speter  dir_context_t *dir;
1624251881Speter
1625251881Speter  dir = apr_pcalloc(dir_pool, sizeof(*dir));
1626251881Speter
1627251881Speter  dir->pool = dir_pool;
1628251881Speter
1629251881Speter  dir->parent_dir = parent;
1630299742Sdim  dir->commit_ctx = parent->commit_ctx;
1631251881Speter
1632251881Speter  dir->added = FALSE;
1633251881Speter  dir->base_revision = base_revision;
1634251881Speter  dir->relpath = apr_pstrdup(dir->pool, path);
1635251881Speter  dir->name = svn_relpath_basename(dir->relpath, NULL);
1636299742Sdim  dir->prop_changes = apr_hash_make(dir->pool);
1637251881Speter
1638299742Sdim  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1639251881Speter    {
1640299742Sdim      dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1641251881Speter                                             path, dir->pool);
1642251881Speter    }
1643251881Speter  else
1644251881Speter    {
1645251881Speter      SVN_ERR(get_version_url(&dir->url,
1646299742Sdim                              dir->commit_ctx->session,
1647251881Speter                              dir->relpath, dir->base_revision,
1648299742Sdim                              dir->commit_ctx->checked_in_url,
1649251881Speter                              dir->pool, dir->pool /* scratch_pool */));
1650251881Speter    }
1651251881Speter  *child_baton = dir;
1652251881Speter
1653251881Speter  return SVN_NO_ERROR;
1654251881Speter}
1655251881Speter
1656251881Speterstatic svn_error_t *
1657251881Speterchange_dir_prop(void *dir_baton,
1658251881Speter                const char *name,
1659251881Speter                const svn_string_t *value,
1660299742Sdim                apr_pool_t *scratch_pool)
1661251881Speter{
1662251881Speter  dir_context_t *dir = dir_baton;
1663299742Sdim  svn_prop_t *prop;
1664251881Speter
1665299742Sdim  if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1666251881Speter    {
1667251881Speter      /* Ensure we have a checked out dir. */
1668299742Sdim      SVN_ERR(checkout_dir(dir, scratch_pool));
1669251881Speter    }
1670251881Speter
1671299742Sdim  prop = apr_palloc(dir->pool, sizeof(*prop));
1672251881Speter
1673299742Sdim  prop->name = apr_pstrdup(dir->pool, name);
1674299742Sdim  prop->value = svn_string_dup(value, dir->pool);
1675251881Speter
1676299742Sdim  svn_hash_sets(dir->prop_changes, prop->name, prop);
1677299742Sdim
1678251881Speter  return SVN_NO_ERROR;
1679251881Speter}
1680251881Speter
1681251881Speterstatic svn_error_t *
1682251881Speterclose_directory(void *dir_baton,
1683251881Speter                apr_pool_t *pool)
1684251881Speter{
1685251881Speter  dir_context_t *dir = dir_baton;
1686251881Speter
1687251881Speter  /* Huh?  We're going to be called before the texts are sent.  Ugh.
1688251881Speter   * Therefore, just wave politely at our caller.
1689251881Speter   */
1690251881Speter
1691251881Speter  /* PROPPATCH our prop change and pass it along.  */
1692299742Sdim  if (apr_hash_count(dir->prop_changes))
1693251881Speter    {
1694251881Speter      proppatch_context_t *proppatch_ctx;
1695251881Speter
1696251881Speter      proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1697251881Speter      proppatch_ctx->pool = pool;
1698299742Sdim      proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
1699251881Speter      proppatch_ctx->relpath = dir->relpath;
1700299742Sdim      proppatch_ctx->prop_changes = dir->prop_changes;
1701251881Speter      proppatch_ctx->base_revision = dir->base_revision;
1702251881Speter
1703299742Sdim      if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1704251881Speter        {
1705251881Speter          proppatch_ctx->path = dir->url;
1706251881Speter        }
1707251881Speter      else
1708251881Speter        {
1709251881Speter          proppatch_ctx->path = dir->working_url;
1710251881Speter        }
1711251881Speter
1712299742Sdim      SVN_ERR(proppatch_resource(dir->commit_ctx->session,
1713299742Sdim                                 proppatch_ctx, dir->pool));
1714251881Speter    }
1715251881Speter
1716251881Speter  return SVN_NO_ERROR;
1717251881Speter}
1718251881Speter
1719251881Speterstatic svn_error_t *
1720251881Speteradd_file(const char *path,
1721251881Speter         void *parent_baton,
1722251881Speter         const char *copy_path,
1723251881Speter         svn_revnum_t copy_revision,
1724251881Speter         apr_pool_t *file_pool,
1725251881Speter         void **file_baton)
1726251881Speter{
1727251881Speter  dir_context_t *dir = parent_baton;
1728251881Speter  file_context_t *new_file;
1729251881Speter  const char *deleted_parent = path;
1730299742Sdim  apr_pool_t *scratch_pool = svn_pool_create(file_pool);
1731251881Speter
1732251881Speter  new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1733251881Speter  new_file->pool = file_pool;
1734251881Speter
1735251881Speter  dir->ref_count++;
1736251881Speter
1737251881Speter  new_file->parent_dir = dir;
1738299742Sdim  new_file->commit_ctx = dir->commit_ctx;
1739251881Speter  new_file->relpath = apr_pstrdup(new_file->pool, path);
1740251881Speter  new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1741251881Speter  new_file->added = TRUE;
1742251881Speter  new_file->base_revision = SVN_INVALID_REVNUM;
1743262253Speter  new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1744251881Speter  new_file->copy_revision = copy_revision;
1745299742Sdim  new_file->prop_changes = apr_hash_make(new_file->pool);
1746251881Speter
1747251881Speter  /* Ensure that the file doesn't exist by doing a HEAD on the
1748251881Speter     resource.  If we're using HTTP v2, we'll just look into the
1749251881Speter     transaction root tree for this thing.  */
1750299742Sdim  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1751251881Speter    {
1752299742Sdim      new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
1753251881Speter                                                  path, new_file->pool);
1754251881Speter    }
1755251881Speter  else
1756251881Speter    {
1757251881Speter      /* Ensure our parent directory has been checked out */
1758299742Sdim      SVN_ERR(checkout_dir(dir, scratch_pool));
1759251881Speter
1760251881Speter      new_file->url =
1761251881Speter        svn_path_url_add_component2(dir->working_url,
1762251881Speter                                    new_file->name, new_file->pool);
1763251881Speter    }
1764251881Speter
1765251881Speter  while (deleted_parent && deleted_parent[0] != '\0')
1766251881Speter    {
1767299742Sdim      if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
1768251881Speter        {
1769251881Speter          break;
1770251881Speter        }
1771251881Speter      deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1772251881Speter    }
1773251881Speter
1774299742Sdim  if (copy_path)
1775251881Speter    {
1776251881Speter      svn_ra_serf__handler_t *handler;
1777299742Sdim      apr_uri_t uri;
1778299742Sdim      const char *req_url;
1779299742Sdim      apr_status_t status;
1780251881Speter
1781299742Sdim      /* Create the copy directly as cheap 'does exist/out of date'
1782299742Sdim         check. We update the copy (if needed) from close_file() */
1783299742Sdim
1784299742Sdim      status = apr_uri_parse(scratch_pool, copy_path, &uri);
1785299742Sdim      if (status)
1786299742Sdim        return svn_ra_serf__wrap_err(status, NULL);
1787299742Sdim
1788299742Sdim      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1789299742Sdim                                          dir->commit_ctx->session,
1790299742Sdim                                          uri.path, copy_revision,
1791299742Sdim                                          scratch_pool, scratch_pool));
1792299742Sdim
1793299742Sdim      handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1794299742Sdim                                            scratch_pool);
1795299742Sdim      handler->method = "COPY";
1796299742Sdim      handler->path = req_url;
1797299742Sdim
1798299742Sdim      handler->response_handler = svn_ra_serf__expect_empty_body;
1799299742Sdim      handler->response_baton = handler;
1800299742Sdim
1801299742Sdim      handler->header_delegate = setup_copy_file_headers;
1802299742Sdim      handler->header_delegate_baton = new_file;
1803299742Sdim
1804299742Sdim      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1805299742Sdim
1806299742Sdim      if (handler->sline.code != 201)
1807299742Sdim        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1808299742Sdim    }
1809299742Sdim  else if (! ((dir->added && !dir->copy_path) ||
1810299742Sdim           (deleted_parent && deleted_parent[0] != '\0')))
1811299742Sdim    {
1812299742Sdim      svn_ra_serf__handler_t *handler;
1813299742Sdim      svn_error_t *err;
1814299742Sdim
1815299742Sdim      handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1816299742Sdim                                            scratch_pool);
1817251881Speter      handler->method = "HEAD";
1818251881Speter      handler->path = svn_path_url_add_component2(
1819299742Sdim                                        dir->commit_ctx->session->session_url.path,
1820299742Sdim                                        path, scratch_pool);
1821251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
1822251881Speter      handler->response_baton = handler;
1823299742Sdim      handler->no_dav_headers = TRUE; /* Read only operation outside txn */
1824251881Speter
1825299742Sdim      err = svn_ra_serf__context_run_one(handler, scratch_pool);
1826251881Speter
1827299742Sdim      if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1828251881Speter        {
1829299742Sdim          svn_error_clear(err); /* Great. We can create a new file! */
1830251881Speter        }
1831299742Sdim      else if (err)
1832299742Sdim        return svn_error_trace(err);
1833299742Sdim      else
1834299742Sdim        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1835299742Sdim                                 _("File '%s' already exists"), path);
1836251881Speter    }
1837251881Speter
1838299742Sdim  svn_pool_destroy(scratch_pool);
1839251881Speter  *file_baton = new_file;
1840251881Speter
1841251881Speter  return SVN_NO_ERROR;
1842251881Speter}
1843251881Speter
1844251881Speterstatic svn_error_t *
1845251881Speteropen_file(const char *path,
1846251881Speter          void *parent_baton,
1847251881Speter          svn_revnum_t base_revision,
1848251881Speter          apr_pool_t *file_pool,
1849251881Speter          void **file_baton)
1850251881Speter{
1851251881Speter  dir_context_t *parent = parent_baton;
1852251881Speter  file_context_t *new_file;
1853251881Speter
1854251881Speter  new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1855251881Speter  new_file->pool = file_pool;
1856251881Speter
1857251881Speter  parent->ref_count++;
1858251881Speter
1859251881Speter  new_file->parent_dir = parent;
1860299742Sdim  new_file->commit_ctx = parent->commit_ctx;
1861251881Speter  new_file->relpath = apr_pstrdup(new_file->pool, path);
1862251881Speter  new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1863251881Speter  new_file->added = FALSE;
1864251881Speter  new_file->base_revision = base_revision;
1865299742Sdim  new_file->prop_changes = apr_hash_make(new_file->pool);
1866251881Speter
1867299742Sdim  if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
1868251881Speter    {
1869299742Sdim      new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1870251881Speter                                                  path, new_file->pool);
1871251881Speter    }
1872251881Speter  else
1873251881Speter    {
1874251881Speter      /* CHECKOUT the file into our activity. */
1875251881Speter      SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1876251881Speter
1877251881Speter      new_file->url = new_file->working_url;
1878251881Speter    }
1879251881Speter
1880251881Speter  *file_baton = new_file;
1881251881Speter
1882251881Speter  return SVN_NO_ERROR;
1883251881Speter}
1884251881Speter
1885299742Sdim/* Implements svn_stream_lazyopen_func_t for apply_textdelta */
1886251881Speterstatic svn_error_t *
1887299742Sdimdelayed_commit_stream_open(svn_stream_t **stream,
1888299742Sdim                           void *baton,
1889299742Sdim                           apr_pool_t *result_pool,
1890299742Sdim                           apr_pool_t *scratch_pool)
1891299742Sdim{
1892299742Sdim  file_context_t *file_ctx = baton;
1893299742Sdim
1894299742Sdim  SVN_ERR(svn_io_open_unique_file3(&file_ctx->svndiff, NULL, NULL,
1895299742Sdim                                   svn_io_file_del_on_pool_cleanup,
1896299742Sdim                                   file_ctx->pool, scratch_pool));
1897299742Sdim
1898299742Sdim  *stream = svn_stream_from_aprfile2(file_ctx->svndiff, TRUE, result_pool);
1899299742Sdim  return SVN_NO_ERROR;
1900299742Sdim}
1901299742Sdim
1902299742Sdimstatic svn_error_t *
1903251881Speterapply_textdelta(void *file_baton,
1904251881Speter                const char *base_checksum,
1905251881Speter                apr_pool_t *pool,
1906251881Speter                svn_txdelta_window_handler_t *handler,
1907251881Speter                void **handler_baton)
1908251881Speter{
1909251881Speter  file_context_t *ctx = file_baton;
1910251881Speter
1911251881Speter  /* Store the stream in a temporary file; we'll give it to serf when we
1912251881Speter   * close this file.
1913251881Speter   *
1914251881Speter   * TODO: There should be a way we can stream the request body instead of
1915251881Speter   * writing to a temporary file (ugh). A special svn stream serf bucket
1916251881Speter   * that returns EAGAIN until we receive the done call?  But, when
1917251881Speter   * would we run through the serf context?  Grr.
1918251881Speter   */
1919251881Speter
1920299742Sdim  ctx->stream = svn_stream_lazyopen_create(delayed_commit_stream_open,
1921299742Sdim                                           ctx, FALSE, ctx->pool);
1922251881Speter
1923251881Speter  svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
1924251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
1925251881Speter
1926251881Speter  if (base_checksum)
1927251881Speter    ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
1928251881Speter
1929251881Speter  return SVN_NO_ERROR;
1930251881Speter}
1931251881Speter
1932251881Speterstatic svn_error_t *
1933251881Speterchange_file_prop(void *file_baton,
1934251881Speter                 const char *name,
1935251881Speter                 const svn_string_t *value,
1936251881Speter                 apr_pool_t *pool)
1937251881Speter{
1938251881Speter  file_context_t *file = file_baton;
1939299742Sdim  svn_prop_t *prop;
1940251881Speter
1941299742Sdim  prop = apr_palloc(file->pool, sizeof(*prop));
1942251881Speter
1943299742Sdim  prop->name = apr_pstrdup(file->pool, name);
1944299742Sdim  prop->value = svn_string_dup(value, file->pool);
1945251881Speter
1946299742Sdim  svn_hash_sets(file->prop_changes, prop->name, prop);
1947251881Speter
1948251881Speter  return SVN_NO_ERROR;
1949251881Speter}
1950251881Speter
1951251881Speterstatic svn_error_t *
1952251881Speterclose_file(void *file_baton,
1953251881Speter           const char *text_checksum,
1954251881Speter           apr_pool_t *scratch_pool)
1955251881Speter{
1956251881Speter  file_context_t *ctx = file_baton;
1957251881Speter  svn_boolean_t put_empty_file = FALSE;
1958251881Speter
1959251881Speter  ctx->result_checksum = text_checksum;
1960251881Speter
1961251881Speter  /* If we got no stream of changes, but this is an added-without-history
1962251881Speter   * file, make a note that we'll be PUTting a zero-byte file to the server.
1963251881Speter   */
1964299742Sdim  if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
1965251881Speter    put_empty_file = TRUE;
1966251881Speter
1967251881Speter  /* If we had a stream of changes, push them to the server... */
1968299742Sdim  if (ctx->svndiff || put_empty_file)
1969251881Speter    {
1970251881Speter      svn_ra_serf__handler_t *handler;
1971299742Sdim      int expected_result;
1972251881Speter
1973299742Sdim      handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
1974299742Sdim                                            scratch_pool);
1975299742Sdim
1976251881Speter      handler->method = "PUT";
1977251881Speter      handler->path = ctx->url;
1978251881Speter
1979251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
1980251881Speter      handler->response_baton = handler;
1981251881Speter
1982251881Speter      if (put_empty_file)
1983251881Speter        {
1984251881Speter          handler->body_delegate = create_empty_put_body;
1985251881Speter          handler->body_delegate_baton = ctx;
1986251881Speter          handler->body_type = "text/plain";
1987251881Speter        }
1988251881Speter      else
1989251881Speter        {
1990251881Speter          handler->body_delegate = create_put_body;
1991251881Speter          handler->body_delegate_baton = ctx;
1992251881Speter          handler->body_type = SVN_SVNDIFF_MIME_TYPE;
1993251881Speter        }
1994251881Speter
1995251881Speter      handler->header_delegate = setup_put_headers;
1996251881Speter      handler->header_delegate_baton = ctx;
1997251881Speter
1998251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1999251881Speter
2000299742Sdim      if (ctx->added && ! ctx->copy_path)
2001299742Sdim        expected_result = 201; /* Created */
2002299742Sdim      else
2003299742Sdim        expected_result = 204; /* Updated */
2004299742Sdim
2005299742Sdim      if (handler->sline.code != expected_result)
2006299742Sdim        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2007251881Speter    }
2008251881Speter
2009251881Speter  if (ctx->svndiff)
2010251881Speter    SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2011251881Speter
2012251881Speter  /* If we had any prop changes, push them via PROPPATCH. */
2013299742Sdim  if (apr_hash_count(ctx->prop_changes))
2014251881Speter    {
2015251881Speter      proppatch_context_t *proppatch;
2016251881Speter
2017299742Sdim      proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
2018299742Sdim      proppatch->pool = scratch_pool;
2019251881Speter      proppatch->relpath = ctx->relpath;
2020251881Speter      proppatch->path = ctx->url;
2021299742Sdim      proppatch->commit_ctx = ctx->commit_ctx;
2022299742Sdim      proppatch->prop_changes = ctx->prop_changes;
2023251881Speter      proppatch->base_revision = ctx->base_revision;
2024251881Speter
2025299742Sdim      SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
2026299742Sdim                                 proppatch, scratch_pool));
2027251881Speter    }
2028251881Speter
2029251881Speter  return SVN_NO_ERROR;
2030251881Speter}
2031251881Speter
2032251881Speterstatic svn_error_t *
2033251881Speterclose_edit(void *edit_baton,
2034251881Speter           apr_pool_t *pool)
2035251881Speter{
2036251881Speter  commit_context_t *ctx = edit_baton;
2037251881Speter  const char *merge_target =
2038251881Speter    ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2039251881Speter  const svn_commit_info_t *commit_info;
2040289166Speter  svn_error_t *err = NULL;
2041251881Speter
2042251881Speter  /* MERGE our activity */
2043299742Sdim  SVN_ERR(svn_ra_serf__run_merge(&commit_info,
2044251881Speter                                 ctx->session,
2045251881Speter                                 merge_target,
2046251881Speter                                 ctx->lock_tokens,
2047251881Speter                                 ctx->keep_locks,
2048251881Speter                                 pool, pool));
2049251881Speter
2050289166Speter  ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
2051289166Speter
2052251881Speter  /* Inform the WC that we did a commit.  */
2053251881Speter  if (ctx->callback)
2054289166Speter    err = ctx->callback(commit_info, ctx->callback_baton, pool);
2055251881Speter
2056251881Speter  /* If we're using activities, DELETE our completed activity.  */
2057251881Speter  if (ctx->activity_url)
2058251881Speter    {
2059251881Speter      svn_ra_serf__handler_t *handler;
2060251881Speter
2061299742Sdim      handler = svn_ra_serf__create_handler(ctx->session, pool);
2062299742Sdim
2063251881Speter      handler->method = "DELETE";
2064251881Speter      handler->path = ctx->activity_url;
2065251881Speter
2066251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
2067251881Speter      handler->response_baton = handler;
2068251881Speter
2069289166Speter      ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
2070251881Speter
2071289166Speter      SVN_ERR(svn_error_compose_create(
2072289166Speter                  err,
2073289166Speter                  svn_ra_serf__context_run_one(handler, pool)));
2074289166Speter
2075299742Sdim      if (handler->sline.code != 204)
2076299742Sdim        return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2077251881Speter    }
2078251881Speter
2079289166Speter  SVN_ERR(err);
2080289166Speter
2081251881Speter  return SVN_NO_ERROR;
2082251881Speter}
2083251881Speter
2084251881Speterstatic svn_error_t *
2085251881Speterabort_edit(void *edit_baton,
2086251881Speter           apr_pool_t *pool)
2087251881Speter{
2088251881Speter  commit_context_t *ctx = edit_baton;
2089251881Speter  svn_ra_serf__handler_t *handler;
2090251881Speter
2091251881Speter  /* If an activity or transaction wasn't even created, don't bother
2092251881Speter     trying to delete it. */
2093251881Speter  if (! (ctx->activity_url || ctx->txn_url))
2094251881Speter    return SVN_NO_ERROR;
2095251881Speter
2096251881Speter  /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2097251881Speter     had a problem. We need to reset it, in order to use it again.  */
2098251881Speter  serf_connection_reset(ctx->session->conns[0]->conn);
2099251881Speter
2100251881Speter  /* DELETE our aborted activity */
2101299742Sdim  handler = svn_ra_serf__create_handler(ctx->session, pool);
2102299742Sdim
2103251881Speter  handler->method = "DELETE";
2104251881Speter
2105251881Speter  handler->response_handler = svn_ra_serf__expect_empty_body;
2106251881Speter  handler->response_baton = handler;
2107299742Sdim  handler->no_fail_on_http_failure_status = TRUE;
2108251881Speter
2109251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2110251881Speter    handler->path = ctx->txn_url;
2111251881Speter  else
2112251881Speter    handler->path = ctx->activity_url;
2113251881Speter
2114251881Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2115251881Speter
2116251881Speter  /* 204 if deleted,
2117251881Speter     403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2118251881Speter     404 if the activity wasn't found. */
2119251881Speter  if (handler->sline.code != 204
2120251881Speter      && handler->sline.code != 403
2121299742Sdim      && handler->sline.code != 404)
2122251881Speter    {
2123299742Sdim      return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2124251881Speter    }
2125251881Speter
2126299742Sdim  /* Don't delete again if somebody aborts twice */
2127299742Sdim  ctx->activity_url = NULL;
2128299742Sdim  ctx->txn_url = NULL;
2129299742Sdim
2130251881Speter  return SVN_NO_ERROR;
2131251881Speter}
2132251881Speter
2133251881Spetersvn_error_t *
2134251881Spetersvn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2135251881Speter                               const svn_delta_editor_t **ret_editor,
2136251881Speter                               void **edit_baton,
2137251881Speter                               apr_hash_t *revprop_table,
2138251881Speter                               svn_commit_callback2_t callback,
2139251881Speter                               void *callback_baton,
2140251881Speter                               apr_hash_t *lock_tokens,
2141251881Speter                               svn_boolean_t keep_locks,
2142251881Speter                               apr_pool_t *pool)
2143251881Speter{
2144251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
2145251881Speter  svn_delta_editor_t *editor;
2146251881Speter  commit_context_t *ctx;
2147251881Speter  const char *repos_root;
2148251881Speter  const char *base_relpath;
2149251881Speter  svn_boolean_t supports_ephemeral_props;
2150251881Speter
2151251881Speter  ctx = apr_pcalloc(pool, sizeof(*ctx));
2152251881Speter
2153251881Speter  ctx->pool = pool;
2154251881Speter
2155251881Speter  ctx->session = session;
2156251881Speter
2157251881Speter  ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2158251881Speter
2159251881Speter  /* If the server supports ephemeral properties, add some carrying
2160251881Speter     interesting version information. */
2161251881Speter  SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2162251881Speter                                      SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2163251881Speter                                      pool));
2164251881Speter  if (supports_ephemeral_props)
2165251881Speter    {
2166251881Speter      svn_hash_sets(ctx->revprop_table,
2167251881Speter                    apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2168251881Speter                    svn_string_create(SVN_VER_NUMBER, pool));
2169251881Speter      svn_hash_sets(ctx->revprop_table,
2170251881Speter                    apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2171251881Speter                    svn_string_create(session->useragent, pool));
2172251881Speter    }
2173251881Speter
2174251881Speter  ctx->callback = callback;
2175251881Speter  ctx->callback_baton = callback_baton;
2176251881Speter
2177269847Speter  ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2178269847Speter                       ? lock_tokens : NULL;
2179251881Speter  ctx->keep_locks = keep_locks;
2180251881Speter
2181251881Speter  ctx->deleted_entries = apr_hash_make(ctx->pool);
2182251881Speter
2183251881Speter  editor = svn_delta_default_editor(pool);
2184251881Speter  editor->open_root = open_root;
2185251881Speter  editor->delete_entry = delete_entry;
2186251881Speter  editor->add_directory = add_directory;
2187251881Speter  editor->open_directory = open_directory;
2188251881Speter  editor->change_dir_prop = change_dir_prop;
2189251881Speter  editor->close_directory = close_directory;
2190251881Speter  editor->add_file = add_file;
2191251881Speter  editor->open_file = open_file;
2192251881Speter  editor->apply_textdelta = apply_textdelta;
2193251881Speter  editor->change_file_prop = change_file_prop;
2194251881Speter  editor->close_file = close_file;
2195251881Speter  editor->close_edit = close_edit;
2196251881Speter  editor->abort_edit = abort_edit;
2197251881Speter
2198251881Speter  *ret_editor = editor;
2199251881Speter  *edit_baton = ctx;
2200251881Speter
2201251881Speter  SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2202251881Speter  base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2203251881Speter                                       pool);
2204251881Speter
2205251881Speter  SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2206251881Speter                                   *edit_baton, repos_root, base_relpath,
2207251881Speter                                   session->shim_callbacks, pool, pool));
2208251881Speter
2209251881Speter  return SVN_NO_ERROR;
2210251881Speter}
2211251881Speter
2212251881Spetersvn_error_t *
2213251881Spetersvn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2214251881Speter                             svn_revnum_t rev,
2215251881Speter                             const char *name,
2216251881Speter                             const svn_string_t *const *old_value_p,
2217251881Speter                             const svn_string_t *value,
2218251881Speter                             apr_pool_t *pool)
2219251881Speter{
2220251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
2221251881Speter  proppatch_context_t *proppatch_ctx;
2222251881Speter  const char *proppatch_target;
2223299742Sdim  const svn_string_t *tmp_old_value;
2224299742Sdim  svn_boolean_t atomic_capable = FALSE;
2225299742Sdim  svn_prop_t *prop;
2226251881Speter  svn_error_t *err;
2227251881Speter
2228299742Sdim  if (old_value_p || !value)
2229299742Sdim    SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable,
2230299742Sdim                                        SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2231299742Sdim                                        pool));
2232299742Sdim
2233251881Speter  if (old_value_p)
2234251881Speter    {
2235251881Speter      /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2236299742Sdim      SVN_ERR_ASSERT(atomic_capable);
2237251881Speter    }
2238299742Sdim  else if (! value && atomic_capable)
2239299742Sdim    {
2240299742Sdim      svn_string_t *old_value;
2241299742Sdim      /* mod_dav_svn doesn't report a failure when a property delete fails. The
2242299742Sdim         atomic revprop change behavior is a nice workaround, to allow getting
2243299742Sdim         access to the error anyway.
2244251881Speter
2245299742Sdim         Somehow the mod_dav maintainers think that returning an error from
2246299742Sdim         mod_dav's property delete is an RFC violation.
2247299742Sdim         See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */
2248251881Speter
2249299742Sdim      SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value,
2250299742Sdim                                    pool));
2251251881Speter
2252299742Sdim      if (!old_value)
2253299742Sdim        return SVN_NO_ERROR; /* Nothing to delete */
2254251881Speter
2255299742Sdim      /* The api expects a double const pointer. Let's make one */
2256299742Sdim      tmp_old_value = old_value;
2257299742Sdim      old_value_p = &tmp_old_value;
2258299742Sdim    }
2259299742Sdim
2260251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2261251881Speter    {
2262251881Speter      proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2263251881Speter    }
2264251881Speter  else
2265251881Speter    {
2266251881Speter      const char *vcc_url;
2267251881Speter
2268299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
2269251881Speter
2270251881Speter      SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2271299742Sdim                                          session, vcc_url, rev, "href",
2272251881Speter                                          pool, pool));
2273251881Speter    }
2274251881Speter
2275251881Speter  /* PROPPATCH our log message and pass it along.  */
2276251881Speter  proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2277251881Speter  proppatch_ctx->pool = pool;
2278299742Sdim  proppatch_ctx->commit_ctx = NULL; /* No lock headers */
2279251881Speter  proppatch_ctx->path = proppatch_target;
2280299742Sdim  proppatch_ctx->prop_changes = apr_hash_make(pool);
2281251881Speter  proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2282251881Speter
2283299742Sdim  if (old_value_p)
2284251881Speter    {
2285299742Sdim      prop = apr_palloc(pool, sizeof (*prop));
2286251881Speter
2287299742Sdim      prop->name = name;
2288299742Sdim      prop->value = *old_value_p;
2289299742Sdim
2290299742Sdim      proppatch_ctx->old_props = apr_hash_make(pool);
2291299742Sdim      svn_hash_sets(proppatch_ctx->old_props, prop->name, prop);
2292251881Speter    }
2293251881Speter
2294299742Sdim  prop = apr_palloc(pool, sizeof (*prop));
2295299742Sdim
2296299742Sdim  prop->name = name;
2297299742Sdim  prop->value = value;
2298299742Sdim  svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
2299299742Sdim
2300299742Sdim  err = proppatch_resource(session, proppatch_ctx, pool);
2301299742Sdim
2302299742Sdim  /* Use specific error code for old property value mismatch.
2303299742Sdim     Use loop to provide the right result with tracing */
2304299742Sdim  if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2305251881Speter    {
2306299742Sdim      svn_error_t *e = err;
2307251881Speter
2308299742Sdim      while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2309299742Sdim        {
2310299742Sdim          e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
2311299742Sdim          e = e->child;
2312299742Sdim        }
2313251881Speter    }
2314251881Speter
2315299742Sdim  return svn_error_trace(err);
2316251881Speter}
2317