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  svn_ra_serf__connection_t *conn;
55251881Speter
56251881Speter  apr_hash_t *revprop_table;
57251881Speter
58251881Speter  svn_commit_callback2_t callback;
59251881Speter  void *callback_baton;
60251881Speter
61251881Speter  apr_hash_t *lock_tokens;
62251881Speter  svn_boolean_t keep_locks;
63251881Speter  apr_hash_t *deleted_entries;   /* deleted files (for delete+add detection) */
64251881Speter
65251881Speter  /* HTTP v2 stuff */
66251881Speter  const char *txn_url;           /* txn URL (!svn/txn/TXN_NAME) */
67251881Speter  const char *txn_root_url;      /* commit anchor txn root URL */
68251881Speter
69251881Speter  /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
70251881Speter  const char *activity_url;      /* activity base URL... */
71251881Speter  const char *baseline_url;      /* the working-baseline resource */
72251881Speter  const char *checked_in_url;    /* checked-in root to base CHECKOUTs from */
73251881Speter  const char *vcc_url;           /* vcc url */
74251881Speter
75251881Speter} commit_context_t;
76251881Speter
77251881Speter#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
78251881Speter
79251881Speter/* Structure associated with a PROPPATCH request. */
80251881Spetertypedef struct proppatch_context_t {
81251881Speter  apr_pool_t *pool;
82251881Speter
83251881Speter  const char *relpath;
84251881Speter  const char *path;
85251881Speter
86251881Speter  commit_context_t *commit;
87251881Speter
88251881Speter  /* Changed and removed properties. */
89251881Speter  apr_hash_t *changed_props;
90251881Speter  apr_hash_t *removed_props;
91251881Speter
92251881Speter  /* Same, for the old value (*old_value_p). */
93251881Speter  apr_hash_t *previous_changed_props;
94251881Speter  apr_hash_t *previous_removed_props;
95251881Speter
96251881Speter  /* In HTTP v2, this is the file/directory version we think we're changing. */
97251881Speter  svn_revnum_t base_revision;
98251881Speter
99251881Speter} proppatch_context_t;
100251881Speter
101251881Spetertypedef struct delete_context_t {
102251881Speter  const char *path;
103251881Speter
104251881Speter  svn_revnum_t revision;
105251881Speter
106251881Speter  const char *lock_token;
107251881Speter  apr_hash_t *lock_token_hash;
108251881Speter  svn_boolean_t keep_locks;
109251881Speter
110251881Speter} delete_context_t;
111251881Speter
112251881Speter/* Represents a directory. */
113251881Spetertypedef struct dir_context_t {
114251881Speter  /* Pool for our directory. */
115251881Speter  apr_pool_t *pool;
116251881Speter
117251881Speter  /* The root commit we're in progress for. */
118251881Speter  commit_context_t *commit;
119251881Speter
120251881Speter  /* URL to operate against (used for CHECKOUT and PROPPATCH before
121251881Speter     HTTP v2, for PROPPATCH in HTTP v2).  */
122251881Speter  const char *url;
123251881Speter
124251881Speter  /* How many pending changes we have left in this directory. */
125251881Speter  unsigned int ref_count;
126251881Speter
127251881Speter  /* Is this directory being added?  (Otherwise, just opened.) */
128251881Speter  svn_boolean_t added;
129251881Speter
130251881Speter  /* Our parent */
131251881Speter  struct dir_context_t *parent_dir;
132251881Speter
133251881Speter  /* The directory name; if "", we're the 'root' */
134251881Speter  const char *relpath;
135251881Speter
136251881Speter  /* The basename of the directory. "" for the 'root' */
137251881Speter  const char *name;
138251881Speter
139251881Speter  /* The base revision of the dir. */
140251881Speter  svn_revnum_t base_revision;
141251881Speter
142251881Speter  const char *copy_path;
143251881Speter  svn_revnum_t copy_revision;
144251881Speter
145251881Speter  /* Changed and removed properties */
146251881Speter  apr_hash_t *changed_props;
147251881Speter  apr_hash_t *removed_props;
148251881Speter
149251881Speter  /* The checked-out working resource for this directory.  May be NULL; if so
150251881Speter     call checkout_dir() first.  */
151251881Speter  const char *working_url;
152251881Speter
153251881Speter} dir_context_t;
154251881Speter
155251881Speter/* Represents a file to be committed. */
156251881Spetertypedef struct file_context_t {
157251881Speter  /* Pool for our file. */
158251881Speter  apr_pool_t *pool;
159251881Speter
160251881Speter  /* The root commit we're in progress for. */
161251881Speter  commit_context_t *commit;
162251881Speter
163251881Speter  /* Is this file being added?  (Otherwise, just opened.) */
164251881Speter  svn_boolean_t added;
165251881Speter
166251881Speter  dir_context_t *parent_dir;
167251881Speter
168251881Speter  const char *relpath;
169251881Speter  const char *name;
170251881Speter
171251881Speter  /* The checked-out working resource for this file. */
172251881Speter  const char *working_url;
173251881Speter
174251881Speter  /* The base revision of the file. */
175251881Speter  svn_revnum_t base_revision;
176251881Speter
177251881Speter  /* Copy path and revision */
178251881Speter  const char *copy_path;
179251881Speter  svn_revnum_t copy_revision;
180251881Speter
181251881Speter  /* stream */
182251881Speter  svn_stream_t *stream;
183251881Speter
184251881Speter  /* Temporary file containing the svndiff. */
185251881Speter  apr_file_t *svndiff;
186251881Speter
187251881Speter  /* Our base checksum as reported by the WC. */
188251881Speter  const char *base_checksum;
189251881Speter
190251881Speter  /* Our resulting checksum as reported by the WC. */
191251881Speter  const char *result_checksum;
192251881Speter
193251881Speter  /* Changed and removed properties. */
194251881Speter  apr_hash_t *changed_props;
195251881Speter  apr_hash_t *removed_props;
196251881Speter
197251881Speter  /* URL to PUT the file at. */
198251881Speter  const char *url;
199251881Speter
200251881Speter} file_context_t;
201251881Speter
202251881Speter
203251881Speter/* Setup routines and handlers for various requests we'll invoke. */
204251881Speter
205251881Speterstatic svn_error_t *
206251881Speterreturn_response_err(svn_ra_serf__handler_t *handler)
207251881Speter{
208251881Speter  svn_error_t *err;
209251881Speter
210251881Speter  /* We should have captured SLINE and LOCATION in the HANDLER.  */
211251881Speter  SVN_ERR_ASSERT(handler->handler_pool != NULL);
212251881Speter
213251881Speter  /* Ye Olde Fallback Error */
214251881Speter  err = svn_error_compose_create(
215251881Speter            handler->server_error != NULL
216251881Speter              ? handler->server_error->error
217251881Speter              : SVN_NO_ERROR,
218251881Speter            svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
219251881Speter                              _("%s of '%s': %d %s"),
220251881Speter                              handler->method, handler->path,
221251881Speter                              handler->sline.code, handler->sline.reason));
222251881Speter
223251881Speter  /* Try to return one of the standard errors for 301, 404, etc.,
224251881Speter     then look for an error embedded in the response.  */
225251881Speter  return svn_error_compose_create(svn_ra_serf__error_on_status(
226253734Speter                                    handler->sline,
227251881Speter                                    handler->path,
228251881Speter                                    handler->location),
229251881Speter                                  err);
230251881Speter}
231251881Speter
232251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
233251881Speterstatic svn_error_t *
234251881Spetercreate_checkout_body(serf_bucket_t **bkt,
235251881Speter                     void *baton,
236251881Speter                     serf_bucket_alloc_t *alloc,
237251881Speter                     apr_pool_t *pool)
238251881Speter{
239251881Speter  const char *activity_url = baton;
240251881Speter  serf_bucket_t *body_bkt;
241251881Speter
242251881Speter  body_bkt = serf_bucket_aggregate_create(alloc);
243251881Speter
244251881Speter  svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
245251881Speter  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
246251881Speter                                    "xmlns:D", "DAV:",
247251881Speter                                    NULL);
248251881Speter  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
249251881Speter  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
250251881Speter
251251881Speter  SVN_ERR_ASSERT(activity_url != NULL);
252251881Speter  svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
253251881Speter                                     activity_url,
254251881Speter                                     strlen(activity_url));
255251881Speter
256251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
257251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
258251881Speter  svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc);
259251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
260251881Speter
261251881Speter  *bkt = body_bkt;
262251881Speter  return SVN_NO_ERROR;
263251881Speter}
264251881Speter
265251881Speter
266251881Speter/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
267251881Speter   given COMMIT_CTX. The resulting working resource will be returned in
268251881Speter   *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
269251881Speter   are performed in SCRATCH_POOL.
270251881Speter
271251881Speter   ### are these URLs actually repos relpath values? or fspath? or maybe
272251881Speter   ### the abspath portion of the full URL.
273251881Speter
274251881Speter   This function operates synchronously.
275251881Speter
276251881Speter   Strictly speaking, we could perform "all" of the CHECKOUT requests
277251881Speter   when the commit starts, and only block when we need a specific
278251881Speter   answer. Or, at a minimum, send off these individual requests async
279251881Speter   and block when we need the answer (eg PUT or PROPPATCH).
280251881Speter
281251881Speter   However: the investment to speed this up is not worthwhile, given
282251881Speter   that CHECKOUT (and the related round trip) is completely obviated
283251881Speter   in HTTPv2.
284251881Speter*/
285251881Speterstatic svn_error_t *
286251881Spetercheckout_node(const char **working_url,
287251881Speter              const commit_context_t *commit_ctx,
288251881Speter              const char *node_url,
289251881Speter              apr_pool_t *result_pool,
290251881Speter              apr_pool_t *scratch_pool)
291251881Speter{
292251881Speter  svn_ra_serf__handler_t handler = { 0 };
293251881Speter  apr_status_t status;
294251881Speter  apr_uri_t uri;
295251881Speter
296251881Speter  /* HANDLER_POOL is the scratch pool since we don't need to remember
297251881Speter     anything from the handler. We just want the working resource.  */
298251881Speter  handler.handler_pool = scratch_pool;
299251881Speter  handler.session = commit_ctx->session;
300251881Speter  handler.conn = commit_ctx->conn;
301251881Speter
302251881Speter  handler.body_delegate = create_checkout_body;
303251881Speter  handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
304251881Speter  handler.body_type = "text/xml";
305251881Speter
306251881Speter  handler.response_handler = svn_ra_serf__expect_empty_body;
307251881Speter  handler.response_baton = &handler;
308251881Speter
309251881Speter  handler.method = "CHECKOUT";
310251881Speter  handler.path = node_url;
311251881Speter
312251881Speter  SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
313251881Speter
314251881Speter  if (handler.sline.code != 201)
315251881Speter    return svn_error_trace(return_response_err(&handler));
316251881Speter
317251881Speter  if (handler.location == NULL)
318251881Speter    return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
319251881Speter                            _("No Location header received"));
320251881Speter
321251881Speter  /* We only want the path portion of the Location header.
322251881Speter     (code.google.com sometimes returns an 'http:' scheme for an
323251881Speter     'https:' transaction ... we'll work around that by stripping the
324251881Speter     scheme, host, and port here and re-adding the correct ones
325251881Speter     later.  */
326251881Speter  status = apr_uri_parse(scratch_pool, handler.location, &uri);
327251881Speter  if (status)
328251881Speter    return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
329251881Speter                            _("Error parsing Location header value"));
330251881Speter
331251881Speter  *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
332251881Speter
333251881Speter  return SVN_NO_ERROR;
334251881Speter}
335251881Speter
336251881Speter
337251881Speter/* This is a wrapper around checkout_node() (which see for
338251881Speter   documentation) which simply retries the CHECKOUT request when it
339251881Speter   fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
340251881Speter   server.
341251881Speter
342251881Speter   See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
343251881Speter   details.
344251881Speter*/
345251881Speterstatic svn_error_t *
346251881Speterretry_checkout_node(const char **working_url,
347251881Speter                    const commit_context_t *commit_ctx,
348251881Speter                    const char *node_url,
349251881Speter                    apr_pool_t *result_pool,
350251881Speter                    apr_pool_t *scratch_pool)
351251881Speter{
352251881Speter  svn_error_t *err = SVN_NO_ERROR;
353251881Speter  int retry_count = 5; /* Magic, arbitrary number. */
354251881Speter
355251881Speter  do
356251881Speter    {
357251881Speter      svn_error_clear(err);
358251881Speter
359251881Speter      err = checkout_node(working_url, commit_ctx, node_url,
360251881Speter                          result_pool, scratch_pool);
361251881Speter
362251881Speter      /* There's a small chance of a race condition here if Apache is
363251881Speter         experiencing heavy commit concurrency or if the network has
364251881Speter         long latency.  It's possible that the value of HEAD changed
365251881Speter         between the time we fetched the latest baseline and the time
366251881Speter         we try to CHECKOUT that baseline.  If that happens, Apache
367251881Speter         will throw us a BAD_BASELINE error (deltaV says you can only
368251881Speter         checkout the latest baseline).  We just ignore that specific
369251881Speter         error and retry a few times, asking for the latest baseline
370251881Speter         again. */
371251881Speter      if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
372251881Speter        return err;
373251881Speter    }
374251881Speter  while (err && retry_count--);
375251881Speter
376251881Speter  return err;
377251881Speter}
378251881Speter
379251881Speter
380251881Speterstatic svn_error_t *
381251881Spetercheckout_dir(dir_context_t *dir,
382251881Speter             apr_pool_t *scratch_pool)
383251881Speter{
384251881Speter  svn_error_t *err;
385251881Speter  dir_context_t *p_dir = dir;
386251881Speter  const char *checkout_url;
387251881Speter  const char **working;
388251881Speter
389251881Speter  if (dir->working_url)
390251881Speter    {
391251881Speter      return SVN_NO_ERROR;
392251881Speter    }
393251881Speter
394251881Speter  /* Is this directory or one of our parent dirs newly added?
395251881Speter   * If so, we're already implicitly checked out. */
396251881Speter  while (p_dir)
397251881Speter    {
398251881Speter      if (p_dir->added)
399251881Speter        {
400251881Speter          /* Implicitly checkout this dir now. */
401251881Speter          dir->working_url = svn_path_url_add_component2(
402251881Speter                                   dir->parent_dir->working_url,
403251881Speter                                   dir->name, dir->pool);
404251881Speter          return SVN_NO_ERROR;
405251881Speter        }
406251881Speter      p_dir = p_dir->parent_dir;
407251881Speter    }
408251881Speter
409251881Speter  /* We could be called twice for the root: once to checkout the baseline;
410251881Speter   * once to checkout the directory itself if we need to do so.
411251881Speter   * Note: CHECKOUT_URL should live longer than HANDLER.
412251881Speter   */
413251881Speter  if (!dir->parent_dir && !dir->commit->baseline_url)
414251881Speter    {
415251881Speter      checkout_url = dir->commit->vcc_url;
416251881Speter      working = &dir->commit->baseline_url;
417251881Speter    }
418251881Speter  else
419251881Speter    {
420251881Speter      checkout_url = dir->url;
421251881Speter      working = &dir->working_url;
422251881Speter    }
423251881Speter
424251881Speter  /* Checkout our directory into the activity URL now. */
425251881Speter  err = retry_checkout_node(working, dir->commit, checkout_url,
426251881Speter                            dir->pool, scratch_pool);
427251881Speter  if (err)
428251881Speter    {
429251881Speter      if (err->apr_err == SVN_ERR_FS_CONFLICT)
430251881Speter        SVN_ERR_W(err, apr_psprintf(scratch_pool,
431251881Speter                  _("Directory '%s' is out of date; try updating"),
432251881Speter                  svn_dirent_local_style(dir->relpath, scratch_pool)));
433251881Speter      return err;
434251881Speter    }
435251881Speter
436251881Speter  return SVN_NO_ERROR;
437251881Speter}
438251881Speter
439251881Speter
440251881Speter/* Set *CHECKED_IN_URL to the appropriate DAV version url for
441251881Speter * RELPATH (relative to the root of SESSION).
442251881Speter *
443251881Speter * Try to find this version url in three ways:
444251881Speter * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
445251881Speter * version url from the working copy properties.
446251881Speter * Second, if the version url of the parent directory PARENT_VSN_URL is
447251881Speter * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
448251881Speter * RELPATH.
449251881Speter * Else, fetch the version url for the root of SESSION using CONN and
450251881Speter * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
451251881Speter * with RELPATH.
452251881Speter *
453251881Speter * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
454251881Speter * temporary allocation.
455251881Speter */
456251881Speterstatic svn_error_t *
457251881Speterget_version_url(const char **checked_in_url,
458251881Speter                svn_ra_serf__session_t *session,
459251881Speter                const char *relpath,
460251881Speter                svn_revnum_t base_revision,
461251881Speter                const char *parent_vsn_url,
462251881Speter                apr_pool_t *result_pool,
463251881Speter                apr_pool_t *scratch_pool)
464251881Speter{
465251881Speter  const char *root_checkout;
466251881Speter
467251881Speter  if (session->wc_callbacks->get_wc_prop)
468251881Speter    {
469251881Speter      const svn_string_t *current_version;
470251881Speter
471251881Speter      SVN_ERR(session->wc_callbacks->get_wc_prop(
472251881Speter                session->wc_callback_baton,
473251881Speter                relpath,
474251881Speter                SVN_RA_SERF__WC_CHECKED_IN_URL,
475251881Speter                &current_version, scratch_pool));
476251881Speter
477251881Speter      if (current_version)
478251881Speter        {
479251881Speter          *checked_in_url =
480251881Speter            svn_urlpath__canonicalize(current_version->data, result_pool);
481251881Speter          return SVN_NO_ERROR;
482251881Speter        }
483251881Speter    }
484251881Speter
485251881Speter  if (parent_vsn_url)
486251881Speter    {
487251881Speter      root_checkout = parent_vsn_url;
488251881Speter    }
489251881Speter  else
490251881Speter    {
491251881Speter      const char *propfind_url;
492251881Speter      svn_ra_serf__connection_t *conn = session->conns[0];
493251881Speter
494251881Speter      if (SVN_IS_VALID_REVNUM(base_revision))
495251881Speter        {
496251881Speter          /* mod_dav_svn can't handle the "Label:" header that
497251881Speter             svn_ra_serf__deliver_props() is going to try to use for
498251881Speter             this lookup, so we'll do things the hard(er) way, by
499251881Speter             looking up the version URL from a resource in the
500251881Speter             baseline collection. */
501251881Speter          /* ### conn==NULL for session->conns[0]. same as CONN.  */
502251881Speter          SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
503251881Speter                                              NULL /* latest_revnum */,
504251881Speter                                              session, NULL /* conn */,
505251881Speter                                              NULL /* url */, base_revision,
506251881Speter                                              scratch_pool, scratch_pool));
507251881Speter        }
508251881Speter      else
509251881Speter        {
510251881Speter          propfind_url = session->session_url.path;
511251881Speter        }
512251881Speter
513251881Speter      SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
514251881Speter                                          conn, propfind_url, base_revision,
515251881Speter                                          "checked-in",
516251881Speter                                          scratch_pool, scratch_pool));
517251881Speter      if (!root_checkout)
518251881Speter        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
519251881Speter                                 _("Path '%s' not present"),
520251881Speter                                 session->session_url.path);
521251881Speter
522251881Speter      root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
523251881Speter    }
524251881Speter
525251881Speter  *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
526251881Speter                                                result_pool);
527251881Speter
528251881Speter  return SVN_NO_ERROR;
529251881Speter}
530251881Speter
531251881Speterstatic svn_error_t *
532251881Spetercheckout_file(file_context_t *file,
533251881Speter              apr_pool_t *scratch_pool)
534251881Speter{
535251881Speter  svn_error_t *err;
536251881Speter  dir_context_t *parent_dir = file->parent_dir;
537251881Speter  const char *checkout_url;
538251881Speter
539251881Speter  /* Is one of our parent dirs newly added?  If so, we're already
540251881Speter   * implicitly checked out.
541251881Speter   */
542251881Speter  while (parent_dir)
543251881Speter    {
544251881Speter      if (parent_dir->added)
545251881Speter        {
546251881Speter          /* Implicitly checkout this file now. */
547251881Speter          file->working_url = svn_path_url_add_component2(
548251881Speter                                    parent_dir->working_url,
549251881Speter                                    svn_relpath_skip_ancestor(
550251881Speter                                      parent_dir->relpath, file->relpath),
551251881Speter                                    file->pool);
552251881Speter          return SVN_NO_ERROR;
553251881Speter        }
554251881Speter      parent_dir = parent_dir->parent_dir;
555251881Speter    }
556251881Speter
557251881Speter  SVN_ERR(get_version_url(&checkout_url,
558251881Speter                          file->commit->session,
559251881Speter                          file->relpath, file->base_revision,
560251881Speter                          NULL, scratch_pool, scratch_pool));
561251881Speter
562251881Speter  /* Checkout our file into the activity URL now. */
563251881Speter  err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
564251881Speter                            file->pool, scratch_pool);
565251881Speter  if (err)
566251881Speter    {
567251881Speter      if (err->apr_err == SVN_ERR_FS_CONFLICT)
568251881Speter        SVN_ERR_W(err, apr_psprintf(scratch_pool,
569251881Speter                  _("File '%s' is out of date; try updating"),
570251881Speter                  svn_dirent_local_style(file->relpath, scratch_pool)));
571251881Speter      return err;
572251881Speter    }
573251881Speter
574251881Speter  return SVN_NO_ERROR;
575251881Speter}
576251881Speter
577251881Speter/* Helper function for proppatch_walker() below. */
578251881Speterstatic svn_error_t *
579251881Speterget_encoding_and_cdata(const char **encoding_p,
580251881Speter                       const svn_string_t **encoded_value_p,
581251881Speter                       serf_bucket_alloc_t *alloc,
582251881Speter                       const svn_string_t *value,
583251881Speter                       apr_pool_t *result_pool,
584251881Speter                       apr_pool_t *scratch_pool)
585251881Speter{
586251881Speter  if (value == NULL)
587251881Speter    {
588251881Speter      *encoding_p = NULL;
589251881Speter      *encoded_value_p = NULL;
590251881Speter      return SVN_NO_ERROR;
591251881Speter    }
592251881Speter
593251881Speter  /* If a property is XML-safe, XML-encode it.  Else, base64-encode
594251881Speter     it. */
595251881Speter  if (svn_xml_is_xml_safe(value->data, value->len))
596251881Speter    {
597251881Speter      svn_stringbuf_t *xml_esc = NULL;
598251881Speter      svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
599251881Speter      *encoding_p = NULL;
600251881Speter      *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
601251881Speter    }
602251881Speter  else
603251881Speter    {
604251881Speter      *encoding_p = "base64";
605251881Speter      *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
606251881Speter    }
607251881Speter
608251881Speter  return SVN_NO_ERROR;
609251881Speter}
610251881Speter
611251881Spetertypedef struct walker_baton_t {
612251881Speter  serf_bucket_t *body_bkt;
613251881Speter  apr_pool_t *body_pool;
614251881Speter
615251881Speter  apr_hash_t *previous_changed_props;
616251881Speter  apr_hash_t *previous_removed_props;
617251881Speter
618251881Speter  const char *path;
619251881Speter
620251881Speter  /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
621251881Speter     rather than D:remove...  (see notes/http-and-webdav/webdav-protocol) */
622251881Speter  enum {
623251881Speter    filter_all_props,
624251881Speter    filter_props_with_old_value,
625251881Speter    filter_props_without_old_value
626251881Speter  } filter;
627251881Speter
628251881Speter  /* Is the property being deleted? */
629251881Speter  svn_boolean_t deleting;
630251881Speter} walker_baton_t;
631251881Speter
632251881Speter/* If we have (recorded in WB) the old value of the property named NS:NAME,
633251881Speter * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
634251881Speter * (which may be NULL); else set *HAVE_OLD_VAL to FALSE.  */
635251881Speterstatic svn_error_t *
636251881Speterderive_old_val(svn_boolean_t *have_old_val,
637251881Speter               const svn_string_t **old_val_p,
638251881Speter               walker_baton_t *wb,
639251881Speter               const char *ns,
640251881Speter               const char *name)
641251881Speter{
642251881Speter  *have_old_val = FALSE;
643251881Speter
644251881Speter  if (wb->previous_changed_props)
645251881Speter    {
646251881Speter      const svn_string_t *val;
647251881Speter      val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
648251881Speter                                         wb->path, ns, name);
649251881Speter      if (val)
650251881Speter        {
651251881Speter          *have_old_val = TRUE;
652251881Speter          *old_val_p = val;
653251881Speter        }
654251881Speter    }
655251881Speter
656251881Speter  if (wb->previous_removed_props)
657251881Speter    {
658251881Speter      const svn_string_t *val;
659251881Speter      val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
660251881Speter                                         wb->path, ns, name);
661251881Speter      if (val)
662251881Speter        {
663251881Speter          *have_old_val = TRUE;
664251881Speter          *old_val_p = NULL;
665251881Speter        }
666251881Speter    }
667251881Speter
668251881Speter  return SVN_NO_ERROR;
669251881Speter}
670251881Speter
671251881Speterstatic svn_error_t *
672251881Speterproppatch_walker(void *baton,
673251881Speter                 const char *ns,
674251881Speter                 const char *name,
675251881Speter                 const svn_string_t *val,
676251881Speter                 apr_pool_t *scratch_pool)
677251881Speter{
678251881Speter  walker_baton_t *wb = baton;
679251881Speter  serf_bucket_t *body_bkt = wb->body_bkt;
680251881Speter  serf_bucket_t *cdata_bkt;
681251881Speter  serf_bucket_alloc_t *alloc;
682251881Speter  const char *encoding;
683251881Speter  svn_boolean_t have_old_val;
684251881Speter  const svn_string_t *old_val;
685251881Speter  const svn_string_t *encoded_value;
686251881Speter  const char *prop_name;
687251881Speter
688251881Speter  SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
689251881Speter
690251881Speter  /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
691251881Speter   * representation. */
692251881Speter  if (wb->filter != filter_all_props)
693251881Speter    {
694251881Speter      if (wb->filter == filter_props_with_old_value && ! have_old_val)
695251881Speter      	return SVN_NO_ERROR;
696251881Speter      if (wb->filter == filter_props_without_old_value && have_old_val)
697251881Speter      	return SVN_NO_ERROR;
698251881Speter    }
699251881Speter  if (wb->deleting)
700251881Speter    val = NULL;
701251881Speter
702251881Speter  alloc = body_bkt->allocator;
703251881Speter
704251881Speter  SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
705251881Speter                                 wb->body_pool, scratch_pool));
706251881Speter  if (encoded_value)
707251881Speter    {
708251881Speter      cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
709251881Speter                                                encoded_value->len,
710251881Speter                                                alloc);
711251881Speter    }
712251881Speter  else
713251881Speter    {
714251881Speter      cdata_bkt = NULL;
715251881Speter    }
716251881Speter
717251881Speter  /* Use the namespace prefix instead of adding the xmlns attribute to support
718251881Speter     property names containing ':' */
719251881Speter  if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
720251881Speter    prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
721251881Speter  else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
722251881Speter    prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
723251881Speter
724251881Speter  if (cdata_bkt)
725251881Speter    svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
726251881Speter                                      "V:encoding", encoding,
727251881Speter                                      NULL);
728251881Speter  else
729251881Speter    svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
730251881Speter                                      "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
731251881Speter                                      NULL);
732251881Speter
733251881Speter  if (have_old_val)
734251881Speter    {
735251881Speter      const char *encoding2;
736251881Speter      const svn_string_t *encoded_value2;
737251881Speter      serf_bucket_t *cdata_bkt2;
738251881Speter
739251881Speter      SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
740251881Speter                                     alloc, old_val,
741251881Speter                                     wb->body_pool, scratch_pool));
742251881Speter
743251881Speter      if (encoded_value2)
744251881Speter        {
745251881Speter          cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
746251881Speter                                                     encoded_value2->len,
747251881Speter                                                     alloc);
748251881Speter        }
749251881Speter      else
750251881Speter        {
751251881Speter          cdata_bkt2 = NULL;
752251881Speter        }
753251881Speter
754251881Speter      if (cdata_bkt2)
755251881Speter        svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
756251881Speter                                          "V:" SVN_DAV__OLD_VALUE,
757251881Speter                                          "V:encoding", encoding2,
758251881Speter                                          NULL);
759251881Speter      else
760251881Speter        svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
761251881Speter                                          "V:" SVN_DAV__OLD_VALUE,
762251881Speter                                          "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
763251881Speter                                          NULL);
764251881Speter
765251881Speter      if (cdata_bkt2)
766251881Speter        serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
767251881Speter
768251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
769251881Speter                                         "V:" SVN_DAV__OLD_VALUE);
770251881Speter    }
771251881Speter  if (cdata_bkt)
772251881Speter    serf_bucket_aggregate_append(body_bkt, cdata_bkt);
773251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
774251881Speter
775251881Speter  return SVN_NO_ERROR;
776251881Speter}
777251881Speter
778251881Speter/* Possible add the lock-token "If:" precondition header to HEADERS if
779251881Speter   an examination of COMMIT_CTX and RELPATH indicates that this is the
780251881Speter   right thing to do.
781251881Speter
782251881Speter   Generally speaking, if the client provided a lock token for
783251881Speter   RELPATH, it's the right thing to do.  There is a notable instance
784251881Speter   where this is not the case, however.  If the file at RELPATH was
785251881Speter   explicitly deleted in this commit already, then mod_dav removed its
786251881Speter   lock token when it fielded the DELETE request, so we don't want to
787251881Speter   set the lock precondition again.  (See
788251881Speter   http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
789251881Speter*/
790251881Speterstatic svn_error_t *
791251881Spetermaybe_set_lock_token_header(serf_bucket_t *headers,
792251881Speter                            commit_context_t *commit_ctx,
793251881Speter                            const char *relpath,
794251881Speter                            apr_pool_t *pool)
795251881Speter{
796251881Speter  const char *token;
797251881Speter
798251881Speter  if (! (relpath && commit_ctx->lock_tokens))
799251881Speter    return SVN_NO_ERROR;
800251881Speter
801251881Speter  if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
802251881Speter    {
803251881Speter      token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
804251881Speter      if (token)
805251881Speter        {
806251881Speter          const char *token_header;
807251881Speter          const char *token_uri;
808251881Speter          apr_uri_t uri = commit_ctx->session->session_url;
809251881Speter
810251881Speter          /* Supplying the optional URI affects apache response when
811251881Speter             the lock is broken, see issue 4369.  When present any URI
812251881Speter             must be absolute (RFC 2518 9.4). */
813251881Speter          uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
814251881Speter                                                         pool);
815251881Speter          token_uri = apr_uri_unparse(pool, &uri, 0);
816251881Speter
817251881Speter          token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
818251881Speter                                     (char *)NULL);
819251881Speter          serf_bucket_headers_set(headers, "If", token_header);
820251881Speter        }
821251881Speter    }
822251881Speter
823251881Speter  return SVN_NO_ERROR;
824251881Speter}
825251881Speter
826251881Speterstatic svn_error_t *
827251881Spetersetup_proppatch_headers(serf_bucket_t *headers,
828251881Speter                        void *baton,
829251881Speter                        apr_pool_t *pool)
830251881Speter{
831251881Speter  proppatch_context_t *proppatch = baton;
832251881Speter
833251881Speter  if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
834251881Speter    {
835251881Speter      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
836251881Speter                              apr_psprintf(pool, "%ld",
837251881Speter                                           proppatch->base_revision));
838251881Speter    }
839251881Speter
840251881Speter  SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
841251881Speter                                      proppatch->relpath, pool));
842251881Speter
843251881Speter  return SVN_NO_ERROR;
844251881Speter}
845251881Speter
846251881Speter
847251881Speterstruct proppatch_body_baton_t {
848251881Speter  proppatch_context_t *proppatch;
849251881Speter
850251881Speter  /* Content in the body should be allocated here, to live long enough.  */
851251881Speter  apr_pool_t *body_pool;
852251881Speter};
853251881Speter
854251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
855251881Speterstatic svn_error_t *
856251881Spetercreate_proppatch_body(serf_bucket_t **bkt,
857251881Speter                      void *baton,
858251881Speter                      serf_bucket_alloc_t *alloc,
859251881Speter                      apr_pool_t *scratch_pool)
860251881Speter{
861251881Speter  struct proppatch_body_baton_t *pbb = baton;
862251881Speter  proppatch_context_t *ctx = pbb->proppatch;
863251881Speter  serf_bucket_t *body_bkt;
864251881Speter  walker_baton_t wb = { 0 };
865251881Speter
866251881Speter  body_bkt = serf_bucket_aggregate_create(alloc);
867251881Speter
868251881Speter  svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
869251881Speter  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
870251881Speter                                    "xmlns:D", "DAV:",
871251881Speter                                    "xmlns:V", SVN_DAV_PROP_NS_DAV,
872251881Speter                                    "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
873251881Speter                                    "xmlns:S", SVN_DAV_PROP_NS_SVN,
874251881Speter                                    NULL);
875251881Speter
876251881Speter  wb.body_bkt = body_bkt;
877251881Speter  wb.body_pool = pbb->body_pool;
878251881Speter  wb.previous_changed_props = ctx->previous_changed_props;
879251881Speter  wb.previous_removed_props = ctx->previous_removed_props;
880251881Speter  wb.path = ctx->path;
881251881Speter
882251881Speter  if (apr_hash_count(ctx->changed_props) > 0)
883251881Speter    {
884251881Speter      svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
885251881Speter      svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
886251881Speter
887251881Speter      wb.filter = filter_all_props;
888251881Speter      wb.deleting = FALSE;
889251881Speter      SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
890251881Speter                                          SVN_INVALID_REVNUM,
891251881Speter                                          proppatch_walker, &wb,
892251881Speter                                          scratch_pool));
893251881Speter
894251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
895251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
896251881Speter    }
897251881Speter
898251881Speter  if (apr_hash_count(ctx->removed_props) > 0)
899251881Speter    {
900251881Speter      svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
901251881Speter      svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
902251881Speter
903251881Speter      wb.filter = filter_props_with_old_value;
904251881Speter      wb.deleting = TRUE;
905251881Speter      SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
906251881Speter                                          SVN_INVALID_REVNUM,
907251881Speter                                          proppatch_walker, &wb,
908251881Speter                                          scratch_pool));
909251881Speter
910251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
911251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
912251881Speter    }
913251881Speter
914251881Speter  if (apr_hash_count(ctx->removed_props) > 0)
915251881Speter    {
916251881Speter      svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
917251881Speter      svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
918251881Speter
919251881Speter      wb.filter = filter_props_without_old_value;
920251881Speter      wb.deleting = TRUE;
921251881Speter      SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
922251881Speter                                          SVN_INVALID_REVNUM,
923251881Speter                                          proppatch_walker, &wb,
924251881Speter                                          scratch_pool));
925251881Speter
926251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
927251881Speter      svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
928251881Speter    }
929251881Speter
930251881Speter  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
931251881Speter
932251881Speter  *bkt = body_bkt;
933251881Speter  return SVN_NO_ERROR;
934251881Speter}
935251881Speter
936251881Speterstatic svn_error_t*
937251881Speterproppatch_resource(proppatch_context_t *proppatch,
938251881Speter                   commit_context_t *commit,
939251881Speter                   apr_pool_t *pool)
940251881Speter{
941251881Speter  svn_ra_serf__handler_t *handler;
942251881Speter  struct proppatch_body_baton_t pbb;
943251881Speter
944251881Speter  handler = apr_pcalloc(pool, sizeof(*handler));
945251881Speter  handler->handler_pool = pool;
946251881Speter  handler->method = "PROPPATCH";
947251881Speter  handler->path = proppatch->path;
948251881Speter  handler->conn = commit->conn;
949251881Speter  handler->session = commit->session;
950251881Speter
951251881Speter  handler->header_delegate = setup_proppatch_headers;
952251881Speter  handler->header_delegate_baton = proppatch;
953251881Speter
954251881Speter  pbb.proppatch = proppatch;
955251881Speter  pbb.body_pool = pool;
956251881Speter  handler->body_delegate = create_proppatch_body;
957251881Speter  handler->body_delegate_baton = &pbb;
958251881Speter
959251881Speter  handler->response_handler = svn_ra_serf__handle_multistatus_only;
960251881Speter  handler->response_baton = handler;
961251881Speter
962251881Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
963251881Speter
964251881Speter  if (handler->sline.code != 207
965251881Speter      || (handler->server_error != NULL
966251881Speter          && handler->server_error->error != NULL))
967251881Speter    {
968251881Speter      return svn_error_create(
969251881Speter               SVN_ERR_RA_DAV_PROPPATCH_FAILED,
970251881Speter               return_response_err(handler),
971251881Speter               _("At least one property change failed; repository"
972251881Speter                 " is unchanged"));
973251881Speter    }
974251881Speter
975251881Speter  return SVN_NO_ERROR;
976251881Speter}
977251881Speter
978251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
979251881Speterstatic svn_error_t *
980251881Spetercreate_put_body(serf_bucket_t **body_bkt,
981251881Speter                void *baton,
982251881Speter                serf_bucket_alloc_t *alloc,
983251881Speter                apr_pool_t *pool)
984251881Speter{
985251881Speter  file_context_t *ctx = baton;
986251881Speter  apr_off_t offset;
987251881Speter
988251881Speter  /* We need to flush the file, make it unbuffered (so that it can be
989251881Speter   * zero-copied via mmap), and reset the position before attempting to
990251881Speter   * deliver the file.
991251881Speter   *
992251881Speter   * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
993251881Speter   * and zero-copy the PUT body.  However, on older APR versions, we can't
994251881Speter   * check the buffer status; but serf will fall through and create a file
995251881Speter   * bucket for us on the buffered svndiff handle.
996251881Speter   */
997251881Speter  apr_file_flush(ctx->svndiff);
998251881Speter#if APR_VERSION_AT_LEAST(1, 3, 0)
999251881Speter  apr_file_buffer_set(ctx->svndiff, NULL, 0);
1000251881Speter#endif
1001251881Speter  offset = 0;
1002251881Speter  apr_file_seek(ctx->svndiff, APR_SET, &offset);
1003251881Speter
1004251881Speter  *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
1005251881Speter  return SVN_NO_ERROR;
1006251881Speter}
1007251881Speter
1008251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
1009251881Speterstatic svn_error_t *
1010251881Spetercreate_empty_put_body(serf_bucket_t **body_bkt,
1011251881Speter                      void *baton,
1012251881Speter                      serf_bucket_alloc_t *alloc,
1013251881Speter                      apr_pool_t *pool)
1014251881Speter{
1015251881Speter  *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
1016251881Speter  return SVN_NO_ERROR;
1017251881Speter}
1018251881Speter
1019251881Speterstatic svn_error_t *
1020251881Spetersetup_put_headers(serf_bucket_t *headers,
1021251881Speter                  void *baton,
1022251881Speter                  apr_pool_t *pool)
1023251881Speter{
1024251881Speter  file_context_t *ctx = baton;
1025251881Speter
1026251881Speter  if (SVN_IS_VALID_REVNUM(ctx->base_revision))
1027251881Speter    {
1028251881Speter      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1029251881Speter                              apr_psprintf(pool, "%ld", ctx->base_revision));
1030251881Speter    }
1031251881Speter
1032251881Speter  if (ctx->base_checksum)
1033251881Speter    {
1034251881Speter      serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
1035251881Speter                              ctx->base_checksum);
1036251881Speter    }
1037251881Speter
1038251881Speter  if (ctx->result_checksum)
1039251881Speter    {
1040251881Speter      serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
1041251881Speter                              ctx->result_checksum);
1042251881Speter    }
1043251881Speter
1044251881Speter  SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
1045251881Speter                                      ctx->relpath, pool));
1046251881Speter
1047251881Speter  return APR_SUCCESS;
1048251881Speter}
1049251881Speter
1050251881Speterstatic svn_error_t *
1051251881Spetersetup_copy_file_headers(serf_bucket_t *headers,
1052251881Speter                        void *baton,
1053251881Speter                        apr_pool_t *pool)
1054251881Speter{
1055251881Speter  file_context_t *file = baton;
1056251881Speter  apr_uri_t uri;
1057251881Speter  const char *absolute_uri;
1058251881Speter
1059251881Speter  /* The Dest URI must be absolute.  Bummer. */
1060251881Speter  uri = file->commit->session->session_url;
1061251881Speter  uri.path = (char*)file->url;
1062251881Speter  absolute_uri = apr_uri_unparse(pool, &uri, 0);
1063251881Speter
1064251881Speter  serf_bucket_headers_set(headers, "Destination", absolute_uri);
1065251881Speter
1066251881Speter  serf_bucket_headers_setn(headers, "Depth", "0");
1067251881Speter  serf_bucket_headers_setn(headers, "Overwrite", "T");
1068251881Speter
1069251881Speter  return SVN_NO_ERROR;
1070251881Speter}
1071251881Speter
1072251881Speterstatic svn_error_t *
1073251881Spetersetup_copy_dir_headers(serf_bucket_t *headers,
1074251881Speter                       void *baton,
1075251881Speter                       apr_pool_t *pool)
1076251881Speter{
1077251881Speter  dir_context_t *dir = baton;
1078251881Speter  apr_uri_t uri;
1079251881Speter  const char *absolute_uri;
1080251881Speter
1081251881Speter  /* The Dest URI must be absolute.  Bummer. */
1082251881Speter  uri = dir->commit->session->session_url;
1083251881Speter
1084251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1085251881Speter    {
1086251881Speter      uri.path = (char *)dir->url;
1087251881Speter    }
1088251881Speter  else
1089251881Speter    {
1090251881Speter      uri.path = (char *)svn_path_url_add_component2(
1091251881Speter                                    dir->parent_dir->working_url,
1092251881Speter                                    dir->name, pool);
1093251881Speter    }
1094251881Speter  absolute_uri = apr_uri_unparse(pool, &uri, 0);
1095251881Speter
1096251881Speter  serf_bucket_headers_set(headers, "Destination", absolute_uri);
1097251881Speter
1098251881Speter  serf_bucket_headers_setn(headers, "Depth", "infinity");
1099251881Speter  serf_bucket_headers_setn(headers, "Overwrite", "T");
1100251881Speter
1101251881Speter  /* Implicitly checkout this dir now. */
1102251881Speter  dir->working_url = apr_pstrdup(dir->pool, uri.path);
1103251881Speter
1104251881Speter  return SVN_NO_ERROR;
1105251881Speter}
1106251881Speter
1107251881Speterstatic svn_error_t *
1108251881Spetersetup_delete_headers(serf_bucket_t *headers,
1109251881Speter                     void *baton,
1110251881Speter                     apr_pool_t *pool)
1111251881Speter{
1112251881Speter  delete_context_t *ctx = baton;
1113251881Speter
1114251881Speter  serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1115251881Speter                          apr_ltoa(pool, ctx->revision));
1116251881Speter
1117251881Speter  if (ctx->lock_token_hash)
1118251881Speter    {
1119251881Speter      ctx->lock_token = svn_hash_gets(ctx->lock_token_hash, ctx->path);
1120251881Speter
1121251881Speter      if (ctx->lock_token)
1122251881Speter        {
1123251881Speter          const char *token_header;
1124251881Speter
1125251881Speter          token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
1126251881Speter                                     ctx->lock_token, ">)", (char *)NULL);
1127251881Speter
1128251881Speter          serf_bucket_headers_set(headers, "If", token_header);
1129251881Speter
1130251881Speter          if (ctx->keep_locks)
1131251881Speter            serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1132251881Speter                                     SVN_DAV_OPTION_KEEP_LOCKS);
1133251881Speter        }
1134251881Speter    }
1135251881Speter
1136251881Speter  return SVN_NO_ERROR;
1137251881Speter}
1138251881Speter
1139251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
1140251881Speterstatic svn_error_t *
1141251881Spetercreate_delete_body(serf_bucket_t **body_bkt,
1142251881Speter                   void *baton,
1143251881Speter                   serf_bucket_alloc_t *alloc,
1144251881Speter                   apr_pool_t *pool)
1145251881Speter{
1146251881Speter  delete_context_t *ctx = baton;
1147251881Speter  serf_bucket_t *body;
1148251881Speter
1149251881Speter  body = serf_bucket_aggregate_create(alloc);
1150251881Speter
1151251881Speter  svn_ra_serf__add_xml_header_buckets(body, alloc);
1152251881Speter
1153251881Speter  svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
1154251881Speter                                     body, alloc, pool);
1155251881Speter
1156251881Speter  *body_bkt = body;
1157251881Speter  return SVN_NO_ERROR;
1158251881Speter}
1159251881Speter
1160251881Speter/* Helper function to write the svndiff stream to temporary file. */
1161251881Speterstatic svn_error_t *
1162251881Spetersvndiff_stream_write(void *file_baton,
1163251881Speter                     const char *data,
1164251881Speter                     apr_size_t *len)
1165251881Speter{
1166251881Speter  file_context_t *ctx = file_baton;
1167251881Speter  apr_status_t status;
1168251881Speter
1169251881Speter  status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
1170251881Speter  if (status)
1171251881Speter      return svn_error_wrap_apr(status, _("Failed writing updated file"));
1172251881Speter
1173251881Speter  return SVN_NO_ERROR;
1174251881Speter}
1175251881Speter
1176251881Speter
1177251881Speter
1178251881Speter/* POST against 'me' resource handlers. */
1179251881Speter
1180251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
1181251881Speterstatic svn_error_t *
1182251881Spetercreate_txn_post_body(serf_bucket_t **body_bkt,
1183251881Speter                     void *baton,
1184251881Speter                     serf_bucket_alloc_t *alloc,
1185251881Speter                     apr_pool_t *pool)
1186251881Speter{
1187251881Speter  apr_hash_t *revprops = baton;
1188251881Speter  svn_skel_t *request_skel;
1189251881Speter  svn_stringbuf_t *skel_str;
1190251881Speter
1191251881Speter  request_skel = svn_skel__make_empty_list(pool);
1192251881Speter  if (revprops)
1193251881Speter    {
1194251881Speter      svn_skel_t *proplist_skel;
1195251881Speter
1196251881Speter      SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1197251881Speter      svn_skel__prepend(proplist_skel, request_skel);
1198251881Speter      svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1199251881Speter      skel_str = svn_skel__unparse(request_skel, pool);
1200251881Speter      *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1201251881Speter    }
1202251881Speter  else
1203251881Speter    {
1204251881Speter      *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1205251881Speter    }
1206251881Speter
1207251881Speter  return SVN_NO_ERROR;
1208251881Speter}
1209251881Speter
1210251881Speter/* Implements svn_ra_serf__request_header_delegate_t */
1211251881Speterstatic svn_error_t *
1212251881Spetersetup_post_headers(serf_bucket_t *headers,
1213251881Speter                   void *baton,
1214251881Speter                   apr_pool_t *pool)
1215251881Speter{
1216251881Speter#ifdef SVN_DAV_SEND_VTXN_NAME
1217251881Speter  /* Enable this to exercise the VTXN-NAME code based on a client
1218251881Speter     supplied transaction name. */
1219251881Speter  serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1220251881Speter                          svn_uuid_generate(pool));
1221251881Speter#endif
1222251881Speter
1223251881Speter  return SVN_NO_ERROR;
1224251881Speter}
1225251881Speter
1226251881Speter
1227251881Speter/* Handler baton for POST request. */
1228251881Spetertypedef struct post_response_ctx_t
1229251881Speter{
1230251881Speter  svn_ra_serf__handler_t *handler;
1231251881Speter  commit_context_t *commit_ctx;
1232251881Speter} post_response_ctx_t;
1233251881Speter
1234251881Speter
1235251881Speter/* This implements serf_bucket_headers_do_callback_fn_t.   */
1236251881Speterstatic int
1237251881Speterpost_headers_iterator_callback(void *baton,
1238251881Speter                               const char *key,
1239251881Speter                               const char *val)
1240251881Speter{
1241251881Speter  post_response_ctx_t *prc = baton;
1242251881Speter  commit_context_t *prc_cc = prc->commit_ctx;
1243251881Speter  svn_ra_serf__session_t *sess = prc_cc->session;
1244251881Speter
1245251881Speter  /* If we provided a UUID to the POST request, we should get back
1246251881Speter     from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1247251881Speter     expect the SVN_DAV_TXN_NAME_HEADER.  We certainly don't expect to
1248251881Speter     see both. */
1249251881Speter
1250251881Speter  if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1251251881Speter    {
1252251881Speter      /* Build out txn and txn-root URLs using the txn name we're
1253251881Speter         given, and store the whole lot of it in the commit context.  */
1254251881Speter      prc_cc->txn_url =
1255251881Speter        svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1256251881Speter      prc_cc->txn_root_url =
1257251881Speter        svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1258251881Speter    }
1259251881Speter
1260251881Speter  if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1261251881Speter    {
1262251881Speter      /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1263251881Speter         given, and store the whole lot of it in the commit context.  */
1264251881Speter      prc_cc->txn_url =
1265251881Speter        svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1266251881Speter      prc_cc->txn_root_url =
1267251881Speter        svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1268251881Speter    }
1269251881Speter
1270251881Speter  return 0;
1271251881Speter}
1272251881Speter
1273251881Speter
1274251881Speter/* A custom serf_response_handler_t which is mostly a wrapper around
1275251881Speter   svn_ra_serf__expect_empty_body -- it just notices POST response
1276251881Speter   headers, too.
1277251881Speter
1278251881Speter   Implements svn_ra_serf__response_handler_t */
1279251881Speterstatic svn_error_t *
1280251881Speterpost_response_handler(serf_request_t *request,
1281251881Speter                      serf_bucket_t *response,
1282251881Speter                      void *baton,
1283251881Speter                      apr_pool_t *scratch_pool)
1284251881Speter{
1285251881Speter  post_response_ctx_t *prc = baton;
1286251881Speter  serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1287251881Speter
1288251881Speter  /* Then see which ones we can discover. */
1289251881Speter  serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1290251881Speter
1291251881Speter  /* Execute the 'real' response handler to XML-parse the repsonse body. */
1292251881Speter  return svn_ra_serf__expect_empty_body(request, response,
1293251881Speter                                        prc->handler, scratch_pool);
1294251881Speter}
1295251881Speter
1296251881Speter
1297251881Speter
1298251881Speter/* Commit baton callbacks */
1299251881Speter
1300251881Speterstatic svn_error_t *
1301251881Speteropen_root(void *edit_baton,
1302251881Speter          svn_revnum_t base_revision,
1303251881Speter          apr_pool_t *dir_pool,
1304251881Speter          void **root_baton)
1305251881Speter{
1306251881Speter  commit_context_t *ctx = edit_baton;
1307251881Speter  svn_ra_serf__handler_t *handler;
1308251881Speter  proppatch_context_t *proppatch_ctx;
1309251881Speter  dir_context_t *dir;
1310251881Speter  apr_hash_index_t *hi;
1311251881Speter  const char *proppatch_target = NULL;
1312251881Speter
1313251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
1314251881Speter    {
1315251881Speter      post_response_ctx_t *prc;
1316251881Speter      const char *rel_path;
1317251881Speter      svn_boolean_t post_with_revprops
1318251881Speter        = (NULL != svn_hash_gets(ctx->session->supported_posts,
1319251881Speter                                 "create-txn-with-props"));
1320251881Speter
1321251881Speter      /* Create our activity URL now on the server. */
1322251881Speter      handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1323251881Speter      handler->handler_pool = ctx->pool;
1324251881Speter      handler->method = "POST";
1325251881Speter      handler->body_type = SVN_SKEL_MIME_TYPE;
1326251881Speter      handler->body_delegate = create_txn_post_body;
1327251881Speter      handler->body_delegate_baton =
1328251881Speter        post_with_revprops ? ctx->revprop_table : NULL;
1329251881Speter      handler->header_delegate = setup_post_headers;
1330251881Speter      handler->header_delegate_baton = NULL;
1331251881Speter      handler->path = ctx->session->me_resource;
1332251881Speter      handler->conn = ctx->session->conns[0];
1333251881Speter      handler->session = ctx->session;
1334251881Speter
1335251881Speter      prc = apr_pcalloc(ctx->pool, sizeof(*prc));
1336251881Speter      prc->handler = handler;
1337251881Speter      prc->commit_ctx = ctx;
1338251881Speter
1339251881Speter      handler->response_handler = post_response_handler;
1340251881Speter      handler->response_baton = prc;
1341251881Speter
1342251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1343251881Speter
1344251881Speter      if (handler->sline.code != 201)
1345251881Speter        {
1346251881Speter          apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1347251881Speter
1348251881Speter          switch (handler->sline.code)
1349251881Speter            {
1350251881Speter              case 403:
1351251881Speter                status = SVN_ERR_RA_DAV_FORBIDDEN;
1352251881Speter                break;
1353251881Speter              case 404:
1354251881Speter                status = SVN_ERR_FS_NOT_FOUND;
1355251881Speter                break;
1356251881Speter            }
1357251881Speter
1358251881Speter          return svn_error_createf(status, NULL,
1359251881Speter                                   _("%s of '%s': %d %s (%s://%s)"),
1360251881Speter                                   handler->method, handler->path,
1361251881Speter                                   handler->sline.code, handler->sline.reason,
1362251881Speter                                   ctx->session->session_url.scheme,
1363251881Speter                                   ctx->session->session_url.hostinfo);
1364251881Speter        }
1365251881Speter      if (! (ctx->txn_root_url && ctx->txn_url))
1366251881Speter        {
1367251881Speter          return svn_error_createf(
1368251881Speter            SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1369251881Speter            _("POST request did not return transaction information"));
1370251881Speter        }
1371251881Speter
1372251881Speter      /* Fixup the txn_root_url to point to the anchor of the commit. */
1373251881Speter      SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
1374251881Speter                                             ctx->session->session_url.path,
1375251881Speter                                             ctx->session, NULL, dir_pool));
1376251881Speter      ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
1377251881Speter                                                      rel_path, ctx->pool);
1378251881Speter
1379251881Speter      /* Build our directory baton. */
1380251881Speter      dir = apr_pcalloc(dir_pool, sizeof(*dir));
1381251881Speter      dir->pool = dir_pool;
1382251881Speter      dir->commit = ctx;
1383251881Speter      dir->base_revision = base_revision;
1384251881Speter      dir->relpath = "";
1385251881Speter      dir->name = "";
1386251881Speter      dir->changed_props = apr_hash_make(dir->pool);
1387251881Speter      dir->removed_props = apr_hash_make(dir->pool);
1388251881Speter      dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
1389251881Speter
1390251881Speter      /* If we included our revprops in the POST, we need not
1391251881Speter         PROPPATCH them. */
1392251881Speter      proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
1393251881Speter    }
1394251881Speter  else
1395251881Speter    {
1396251881Speter      const char *activity_str = ctx->session->activity_collection_url;
1397251881Speter
1398251881Speter      if (!activity_str)
1399251881Speter        SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
1400251881Speter                                                        ctx->session->conns[0],
1401251881Speter                                                        ctx->pool,
1402251881Speter                                                        ctx->pool));
1403251881Speter
1404251881Speter      /* Cache the result. */
1405251881Speter      if (activity_str)
1406251881Speter        {
1407251881Speter          ctx->session->activity_collection_url =
1408251881Speter            apr_pstrdup(ctx->session->pool, activity_str);
1409251881Speter        }
1410251881Speter      else
1411251881Speter        {
1412251881Speter          return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1413251881Speter                                  _("The OPTIONS response did not include the "
1414251881Speter                                    "requested activity-collection-set value"));
1415251881Speter        }
1416251881Speter
1417251881Speter      ctx->activity_url =
1418251881Speter        svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
1419251881Speter                                    ctx->pool);
1420251881Speter
1421251881Speter      /* Create our activity URL now on the server. */
1422251881Speter      handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1423251881Speter      handler->handler_pool = ctx->pool;
1424251881Speter      handler->method = "MKACTIVITY";
1425251881Speter      handler->path = ctx->activity_url;
1426251881Speter      handler->conn = ctx->session->conns[0];
1427251881Speter      handler->session = ctx->session;
1428251881Speter
1429251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
1430251881Speter      handler->response_baton = handler;
1431251881Speter
1432251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
1433251881Speter
1434251881Speter      if (handler->sline.code != 201)
1435251881Speter        {
1436251881Speter          apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
1437251881Speter
1438251881Speter          switch (handler->sline.code)
1439251881Speter            {
1440251881Speter              case 403:
1441251881Speter                status = SVN_ERR_RA_DAV_FORBIDDEN;
1442251881Speter                break;
1443251881Speter              case 404:
1444251881Speter                status = SVN_ERR_FS_NOT_FOUND;
1445251881Speter                break;
1446251881Speter            }
1447251881Speter
1448251881Speter          return svn_error_createf(status, NULL,
1449251881Speter                                   _("%s of '%s': %d %s (%s://%s)"),
1450251881Speter                                   handler->method, handler->path,
1451251881Speter                                   handler->sline.code, handler->sline.reason,
1452251881Speter                                   ctx->session->session_url.scheme,
1453251881Speter                                   ctx->session->session_url.hostinfo);
1454251881Speter        }
1455251881Speter
1456251881Speter      /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1457251881Speter      SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
1458251881Speter                                        ctx->conn, ctx->pool));
1459251881Speter
1460251881Speter
1461251881Speter      /* Build our directory baton. */
1462251881Speter      dir = apr_pcalloc(dir_pool, sizeof(*dir));
1463251881Speter      dir->pool = dir_pool;
1464251881Speter      dir->commit = ctx;
1465251881Speter      dir->base_revision = base_revision;
1466251881Speter      dir->relpath = "";
1467251881Speter      dir->name = "";
1468251881Speter      dir->changed_props = apr_hash_make(dir->pool);
1469251881Speter      dir->removed_props = apr_hash_make(dir->pool);
1470251881Speter
1471251881Speter      SVN_ERR(get_version_url(&dir->url, dir->commit->session,
1472251881Speter                              dir->relpath,
1473251881Speter                              dir->base_revision, ctx->checked_in_url,
1474251881Speter                              dir->pool, dir->pool /* scratch_pool */));
1475251881Speter      ctx->checked_in_url = dir->url;
1476251881Speter
1477251881Speter      /* Checkout our root dir */
1478251881Speter      SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
1479251881Speter
1480251881Speter      proppatch_target = ctx->baseline_url;
1481251881Speter    }
1482251881Speter
1483251881Speter  /* Unless this is NULL -- which means we don't need to PROPPATCH the
1484251881Speter     transaction with our revprops -- then, you know, PROPPATCH the
1485251881Speter     transaction with our revprops.  */
1486251881Speter  if (proppatch_target)
1487251881Speter    {
1488251881Speter      proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
1489251881Speter      proppatch_ctx->pool = dir_pool;
1490251881Speter      proppatch_ctx->commit = ctx;
1491251881Speter      proppatch_ctx->path = proppatch_target;
1492251881Speter      proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
1493251881Speter      proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
1494251881Speter      proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1495251881Speter
1496251881Speter      for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
1497251881Speter           hi = apr_hash_next(hi))
1498251881Speter        {
1499251881Speter          const char *name = svn__apr_hash_index_key(hi);
1500251881Speter          svn_string_t *value = svn__apr_hash_index_val(hi);
1501251881Speter          const char *ns;
1502251881Speter
1503251881Speter          if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1504251881Speter            {
1505251881Speter              ns = SVN_DAV_PROP_NS_SVN;
1506251881Speter              name += sizeof(SVN_PROP_PREFIX) - 1;
1507251881Speter            }
1508251881Speter          else
1509251881Speter            {
1510251881Speter              ns = SVN_DAV_PROP_NS_CUSTOM;
1511251881Speter            }
1512251881Speter
1513251881Speter          svn_ra_serf__set_prop(proppatch_ctx->changed_props,
1514251881Speter                                proppatch_ctx->path,
1515251881Speter                                ns, name, value, proppatch_ctx->pool);
1516251881Speter        }
1517251881Speter
1518251881Speter      SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
1519251881Speter    }
1520251881Speter
1521251881Speter  *root_baton = dir;
1522251881Speter
1523251881Speter  return SVN_NO_ERROR;
1524251881Speter}
1525251881Speter
1526251881Speterstatic svn_error_t *
1527251881Speterdelete_entry(const char *path,
1528251881Speter             svn_revnum_t revision,
1529251881Speter             void *parent_baton,
1530251881Speter             apr_pool_t *pool)
1531251881Speter{
1532251881Speter  dir_context_t *dir = parent_baton;
1533251881Speter  delete_context_t *delete_ctx;
1534251881Speter  svn_ra_serf__handler_t *handler;
1535251881Speter  const char *delete_target;
1536251881Speter  svn_error_t *err;
1537251881Speter
1538251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1539251881Speter    {
1540251881Speter      delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
1541251881Speter                                                  path, dir->pool);
1542251881Speter    }
1543251881Speter  else
1544251881Speter    {
1545251881Speter      /* Ensure our directory has been checked out */
1546251881Speter      SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1547251881Speter      delete_target = svn_path_url_add_component2(dir->working_url,
1548251881Speter                                                  svn_relpath_basename(path,
1549251881Speter                                                                       NULL),
1550251881Speter                                                  pool);
1551251881Speter    }
1552251881Speter
1553251881Speter  /* DELETE our entry */
1554251881Speter  delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1555251881Speter  delete_ctx->path = apr_pstrdup(pool, path);
1556251881Speter  delete_ctx->revision = revision;
1557251881Speter  delete_ctx->lock_token_hash = dir->commit->lock_tokens;
1558251881Speter  delete_ctx->keep_locks = dir->commit->keep_locks;
1559251881Speter
1560251881Speter  handler = apr_pcalloc(pool, sizeof(*handler));
1561251881Speter  handler->handler_pool = pool;
1562251881Speter  handler->session = dir->commit->session;
1563251881Speter  handler->conn = dir->commit->conn;
1564251881Speter
1565251881Speter  handler->response_handler = svn_ra_serf__expect_empty_body;
1566251881Speter  handler->response_baton = handler;
1567251881Speter
1568251881Speter  handler->header_delegate = setup_delete_headers;
1569251881Speter  handler->header_delegate_baton = delete_ctx;
1570251881Speter
1571251881Speter  handler->method = "DELETE";
1572251881Speter  handler->path = delete_target;
1573251881Speter
1574251881Speter  err = svn_ra_serf__context_run_one(handler, pool);
1575251881Speter
1576251881Speter  if (err &&
1577251881Speter      (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
1578251881Speter       err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
1579251881Speter       err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
1580251881Speter       err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
1581251881Speter    {
1582251881Speter      svn_error_clear(err);
1583251881Speter
1584251881Speter      /* An error has been registered on the connection. Reset the thing
1585251881Speter         so that we can use it again.  */
1586251881Speter      serf_connection_reset(handler->conn->conn);
1587251881Speter
1588251881Speter      handler->body_delegate = create_delete_body;
1589251881Speter      handler->body_delegate_baton = delete_ctx;
1590251881Speter      handler->body_type = "text/xml";
1591251881Speter
1592251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1593251881Speter    }
1594251881Speter  else if (err)
1595251881Speter    {
1596251881Speter      return err;
1597251881Speter    }
1598251881Speter
1599251881Speter  /* 204 No Content: item successfully deleted */
1600251881Speter  if (handler->sline.code != 204)
1601251881Speter    {
1602251881Speter      return svn_error_trace(return_response_err(handler));
1603251881Speter    }
1604251881Speter
1605251881Speter  svn_hash_sets(dir->commit->deleted_entries,
1606251881Speter                apr_pstrdup(dir->commit->pool, path), (void *)1);
1607251881Speter
1608251881Speter  return SVN_NO_ERROR;
1609251881Speter}
1610251881Speter
1611251881Speterstatic svn_error_t *
1612251881Speteradd_directory(const char *path,
1613251881Speter              void *parent_baton,
1614251881Speter              const char *copyfrom_path,
1615251881Speter              svn_revnum_t copyfrom_revision,
1616251881Speter              apr_pool_t *dir_pool,
1617251881Speter              void **child_baton)
1618251881Speter{
1619251881Speter  dir_context_t *parent = parent_baton;
1620251881Speter  dir_context_t *dir;
1621251881Speter  svn_ra_serf__handler_t *handler;
1622251881Speter  apr_status_t status;
1623251881Speter  const char *mkcol_target;
1624251881Speter
1625251881Speter  dir = apr_pcalloc(dir_pool, sizeof(*dir));
1626251881Speter
1627251881Speter  dir->pool = dir_pool;
1628251881Speter  dir->parent_dir = parent;
1629251881Speter  dir->commit = parent->commit;
1630251881Speter  dir->added = TRUE;
1631251881Speter  dir->base_revision = SVN_INVALID_REVNUM;
1632251881Speter  dir->copy_revision = copyfrom_revision;
1633251881Speter  dir->copy_path = copyfrom_path;
1634251881Speter  dir->relpath = apr_pstrdup(dir->pool, path);
1635251881Speter  dir->name = svn_relpath_basename(dir->relpath, NULL);
1636251881Speter  dir->changed_props = apr_hash_make(dir->pool);
1637251881Speter  dir->removed_props = apr_hash_make(dir->pool);
1638251881Speter
1639251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1640251881Speter    {
1641251881Speter      dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1642251881Speter                                             path, dir->pool);
1643251881Speter      mkcol_target = dir->url;
1644251881Speter    }
1645251881Speter  else
1646251881Speter    {
1647251881Speter      /* Ensure our parent is checked out. */
1648251881Speter      SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1649251881Speter
1650251881Speter      dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
1651251881Speter                                             dir->name, dir->pool);
1652251881Speter      mkcol_target = svn_path_url_add_component2(
1653251881Speter                               parent->working_url,
1654251881Speter                               dir->name, dir->pool);
1655251881Speter    }
1656251881Speter
1657251881Speter  handler = apr_pcalloc(dir->pool, sizeof(*handler));
1658251881Speter  handler->handler_pool = dir->pool;
1659251881Speter  handler->conn = dir->commit->conn;
1660251881Speter  handler->session = dir->commit->session;
1661251881Speter
1662251881Speter  handler->response_handler = svn_ra_serf__expect_empty_body;
1663251881Speter  handler->response_baton = handler;
1664251881Speter  if (!dir->copy_path)
1665251881Speter    {
1666251881Speter      handler->method = "MKCOL";
1667251881Speter      handler->path = mkcol_target;
1668251881Speter    }
1669251881Speter  else
1670251881Speter    {
1671251881Speter      apr_uri_t uri;
1672251881Speter      const char *req_url;
1673251881Speter
1674251881Speter      status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1675251881Speter      if (status)
1676251881Speter        {
1677251881Speter          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1678251881Speter                                   _("Unable to parse URL '%s'"),
1679251881Speter                                   dir->copy_path);
1680251881Speter        }
1681251881Speter
1682251881Speter      /* ### conn==NULL for session->conns[0]. same as commit->conn.  */
1683251881Speter      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1684251881Speter                                          dir->commit->session,
1685251881Speter                                          NULL /* conn */,
1686251881Speter                                          uri.path, dir->copy_revision,
1687251881Speter                                          dir_pool, dir_pool));
1688251881Speter
1689251881Speter      handler->method = "COPY";
1690251881Speter      handler->path = req_url;
1691251881Speter
1692251881Speter      handler->header_delegate = setup_copy_dir_headers;
1693251881Speter      handler->header_delegate_baton = dir;
1694251881Speter    }
1695251881Speter
1696251881Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1697251881Speter
1698251881Speter  switch (handler->sline.code)
1699251881Speter    {
1700251881Speter      case 201: /* Created:    item was successfully copied */
1701251881Speter      case 204: /* No Content: item successfully replaced an existing target */
1702251881Speter        break;
1703251881Speter
1704251881Speter      case 403:
1705251881Speter        return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1706251881Speter                                _("Access to '%s' forbidden"),
1707251881Speter                                 handler->path);
1708251881Speter      default:
1709251881Speter        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1710251881Speter                                 _("Adding directory failed: %s on %s "
1711251881Speter                                   "(%d %s)"),
1712251881Speter                                 handler->method, handler->path,
1713251881Speter                                 handler->sline.code, handler->sline.reason);
1714251881Speter    }
1715251881Speter
1716251881Speter  *child_baton = dir;
1717251881Speter
1718251881Speter  return SVN_NO_ERROR;
1719251881Speter}
1720251881Speter
1721251881Speterstatic svn_error_t *
1722251881Speteropen_directory(const char *path,
1723251881Speter               void *parent_baton,
1724251881Speter               svn_revnum_t base_revision,
1725251881Speter               apr_pool_t *dir_pool,
1726251881Speter               void **child_baton)
1727251881Speter{
1728251881Speter  dir_context_t *parent = parent_baton;
1729251881Speter  dir_context_t *dir;
1730251881Speter
1731251881Speter  dir = apr_pcalloc(dir_pool, sizeof(*dir));
1732251881Speter
1733251881Speter  dir->pool = dir_pool;
1734251881Speter
1735251881Speter  dir->parent_dir = parent;
1736251881Speter  dir->commit = parent->commit;
1737251881Speter
1738251881Speter  dir->added = FALSE;
1739251881Speter  dir->base_revision = base_revision;
1740251881Speter  dir->relpath = apr_pstrdup(dir->pool, path);
1741251881Speter  dir->name = svn_relpath_basename(dir->relpath, NULL);
1742251881Speter  dir->changed_props = apr_hash_make(dir->pool);
1743251881Speter  dir->removed_props = apr_hash_make(dir->pool);
1744251881Speter
1745251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1746251881Speter    {
1747251881Speter      dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1748251881Speter                                             path, dir->pool);
1749251881Speter    }
1750251881Speter  else
1751251881Speter    {
1752251881Speter      SVN_ERR(get_version_url(&dir->url,
1753251881Speter                              dir->commit->session,
1754251881Speter                              dir->relpath, dir->base_revision,
1755251881Speter                              dir->commit->checked_in_url,
1756251881Speter                              dir->pool, dir->pool /* scratch_pool */));
1757251881Speter    }
1758251881Speter  *child_baton = dir;
1759251881Speter
1760251881Speter  return SVN_NO_ERROR;
1761251881Speter}
1762251881Speter
1763251881Speterstatic svn_error_t *
1764251881Speterchange_dir_prop(void *dir_baton,
1765251881Speter                const char *name,
1766251881Speter                const svn_string_t *value,
1767251881Speter                apr_pool_t *pool)
1768251881Speter{
1769251881Speter  dir_context_t *dir = dir_baton;
1770251881Speter  const char *ns;
1771251881Speter  const char *proppatch_target;
1772251881Speter
1773251881Speter
1774251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1775251881Speter    {
1776251881Speter      proppatch_target = dir->url;
1777251881Speter    }
1778251881Speter  else
1779251881Speter    {
1780251881Speter      /* Ensure we have a checked out dir. */
1781251881Speter      SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1782251881Speter
1783251881Speter      proppatch_target = dir->working_url;
1784251881Speter    }
1785251881Speter
1786251881Speter  name = apr_pstrdup(dir->pool, name);
1787251881Speter  if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1788251881Speter    {
1789251881Speter      ns = SVN_DAV_PROP_NS_SVN;
1790251881Speter      name += sizeof(SVN_PROP_PREFIX) - 1;
1791251881Speter    }
1792251881Speter  else
1793251881Speter    {
1794251881Speter      ns = SVN_DAV_PROP_NS_CUSTOM;
1795251881Speter    }
1796251881Speter
1797251881Speter  if (value)
1798251881Speter    {
1799251881Speter      value = svn_string_dup(value, dir->pool);
1800251881Speter      svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
1801251881Speter                            ns, name, value, dir->pool);
1802251881Speter    }
1803251881Speter  else
1804251881Speter    {
1805251881Speter      value = svn_string_create_empty(dir->pool);
1806251881Speter      svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
1807251881Speter                            ns, name, value, dir->pool);
1808251881Speter    }
1809251881Speter
1810251881Speter  return SVN_NO_ERROR;
1811251881Speter}
1812251881Speter
1813251881Speterstatic svn_error_t *
1814251881Speterclose_directory(void *dir_baton,
1815251881Speter                apr_pool_t *pool)
1816251881Speter{
1817251881Speter  dir_context_t *dir = dir_baton;
1818251881Speter
1819251881Speter  /* Huh?  We're going to be called before the texts are sent.  Ugh.
1820251881Speter   * Therefore, just wave politely at our caller.
1821251881Speter   */
1822251881Speter
1823251881Speter  /* PROPPATCH our prop change and pass it along.  */
1824251881Speter  if (apr_hash_count(dir->changed_props) ||
1825251881Speter      apr_hash_count(dir->removed_props))
1826251881Speter    {
1827251881Speter      proppatch_context_t *proppatch_ctx;
1828251881Speter
1829251881Speter      proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1830251881Speter      proppatch_ctx->pool = pool;
1831251881Speter      proppatch_ctx->commit = dir->commit;
1832251881Speter      proppatch_ctx->relpath = dir->relpath;
1833251881Speter      proppatch_ctx->changed_props = dir->changed_props;
1834251881Speter      proppatch_ctx->removed_props = dir->removed_props;
1835251881Speter      proppatch_ctx->base_revision = dir->base_revision;
1836251881Speter
1837251881Speter      if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1838251881Speter        {
1839251881Speter          proppatch_ctx->path = dir->url;
1840251881Speter        }
1841251881Speter      else
1842251881Speter        {
1843251881Speter          proppatch_ctx->path = dir->working_url;
1844251881Speter        }
1845251881Speter
1846251881Speter      SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
1847251881Speter    }
1848251881Speter
1849251881Speter  return SVN_NO_ERROR;
1850251881Speter}
1851251881Speter
1852251881Speterstatic svn_error_t *
1853251881Speteradd_file(const char *path,
1854251881Speter         void *parent_baton,
1855251881Speter         const char *copy_path,
1856251881Speter         svn_revnum_t copy_revision,
1857251881Speter         apr_pool_t *file_pool,
1858251881Speter         void **file_baton)
1859251881Speter{
1860251881Speter  dir_context_t *dir = parent_baton;
1861251881Speter  file_context_t *new_file;
1862251881Speter  const char *deleted_parent = path;
1863251881Speter
1864251881Speter  new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1865251881Speter  new_file->pool = file_pool;
1866251881Speter
1867251881Speter  dir->ref_count++;
1868251881Speter
1869251881Speter  new_file->parent_dir = dir;
1870251881Speter  new_file->commit = dir->commit;
1871251881Speter  new_file->relpath = apr_pstrdup(new_file->pool, path);
1872251881Speter  new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1873251881Speter  new_file->added = TRUE;
1874251881Speter  new_file->base_revision = SVN_INVALID_REVNUM;
1875251881Speter  new_file->copy_path = copy_path;
1876251881Speter  new_file->copy_revision = copy_revision;
1877251881Speter  new_file->changed_props = apr_hash_make(new_file->pool);
1878251881Speter  new_file->removed_props = apr_hash_make(new_file->pool);
1879251881Speter
1880251881Speter  /* Ensure that the file doesn't exist by doing a HEAD on the
1881251881Speter     resource.  If we're using HTTP v2, we'll just look into the
1882251881Speter     transaction root tree for this thing.  */
1883251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
1884251881Speter    {
1885251881Speter      new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
1886251881Speter                                                  path, new_file->pool);
1887251881Speter    }
1888251881Speter  else
1889251881Speter    {
1890251881Speter      /* Ensure our parent directory has been checked out */
1891251881Speter      SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
1892251881Speter
1893251881Speter      new_file->url =
1894251881Speter        svn_path_url_add_component2(dir->working_url,
1895251881Speter                                    new_file->name, new_file->pool);
1896251881Speter    }
1897251881Speter
1898251881Speter  while (deleted_parent && deleted_parent[0] != '\0')
1899251881Speter    {
1900251881Speter      if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
1901251881Speter        {
1902251881Speter          break;
1903251881Speter        }
1904251881Speter      deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1905251881Speter    }
1906251881Speter
1907251881Speter  if (! ((dir->added && !dir->copy_path) ||
1908251881Speter         (deleted_parent && deleted_parent[0] != '\0')))
1909251881Speter    {
1910251881Speter      svn_ra_serf__handler_t *handler;
1911251881Speter
1912251881Speter      handler = apr_pcalloc(new_file->pool, sizeof(*handler));
1913251881Speter      handler->handler_pool = new_file->pool;
1914251881Speter      handler->session = new_file->commit->session;
1915251881Speter      handler->conn = new_file->commit->conn;
1916251881Speter      handler->method = "HEAD";
1917251881Speter      handler->path = svn_path_url_add_component2(
1918251881Speter        dir->commit->session->session_url.path,
1919251881Speter        path, new_file->pool);
1920251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
1921251881Speter      handler->response_baton = handler;
1922251881Speter
1923251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
1924251881Speter
1925251881Speter      if (handler->sline.code != 404)
1926251881Speter        {
1927251881Speter          return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
1928251881Speter                                   _("File '%s' already exists"), path);
1929251881Speter        }
1930251881Speter    }
1931251881Speter
1932251881Speter  *file_baton = new_file;
1933251881Speter
1934251881Speter  return SVN_NO_ERROR;
1935251881Speter}
1936251881Speter
1937251881Speterstatic svn_error_t *
1938251881Speteropen_file(const char *path,
1939251881Speter          void *parent_baton,
1940251881Speter          svn_revnum_t base_revision,
1941251881Speter          apr_pool_t *file_pool,
1942251881Speter          void **file_baton)
1943251881Speter{
1944251881Speter  dir_context_t *parent = parent_baton;
1945251881Speter  file_context_t *new_file;
1946251881Speter
1947251881Speter  new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1948251881Speter  new_file->pool = file_pool;
1949251881Speter
1950251881Speter  parent->ref_count++;
1951251881Speter
1952251881Speter  new_file->parent_dir = parent;
1953251881Speter  new_file->commit = parent->commit;
1954251881Speter  new_file->relpath = apr_pstrdup(new_file->pool, path);
1955251881Speter  new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1956251881Speter  new_file->added = FALSE;
1957251881Speter  new_file->base_revision = base_revision;
1958251881Speter  new_file->changed_props = apr_hash_make(new_file->pool);
1959251881Speter  new_file->removed_props = apr_hash_make(new_file->pool);
1960251881Speter
1961251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
1962251881Speter    {
1963251881Speter      new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url,
1964251881Speter                                                  path, new_file->pool);
1965251881Speter    }
1966251881Speter  else
1967251881Speter    {
1968251881Speter      /* CHECKOUT the file into our activity. */
1969251881Speter      SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1970251881Speter
1971251881Speter      new_file->url = new_file->working_url;
1972251881Speter    }
1973251881Speter
1974251881Speter  *file_baton = new_file;
1975251881Speter
1976251881Speter  return SVN_NO_ERROR;
1977251881Speter}
1978251881Speter
1979251881Speterstatic svn_error_t *
1980251881Speterapply_textdelta(void *file_baton,
1981251881Speter                const char *base_checksum,
1982251881Speter                apr_pool_t *pool,
1983251881Speter                svn_txdelta_window_handler_t *handler,
1984251881Speter                void **handler_baton)
1985251881Speter{
1986251881Speter  file_context_t *ctx = file_baton;
1987251881Speter
1988251881Speter  /* Store the stream in a temporary file; we'll give it to serf when we
1989251881Speter   * close this file.
1990251881Speter   *
1991251881Speter   * TODO: There should be a way we can stream the request body instead of
1992251881Speter   * writing to a temporary file (ugh). A special svn stream serf bucket
1993251881Speter   * that returns EAGAIN until we receive the done call?  But, when
1994251881Speter   * would we run through the serf context?  Grr.
1995251881Speter   *
1996251881Speter   * ctx->pool is the same for all files in the commit that send a
1997251881Speter   * textdelta so this file is explicitly closed in close_file to
1998251881Speter   * avoid too many simultaneously open files.
1999251881Speter   */
2000251881Speter
2001251881Speter  SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
2002251881Speter                                   svn_io_file_del_on_pool_cleanup,
2003251881Speter                                   ctx->pool, pool));
2004251881Speter
2005251881Speter  ctx->stream = svn_stream_create(ctx, pool);
2006251881Speter  svn_stream_set_write(ctx->stream, svndiff_stream_write);
2007251881Speter
2008251881Speter  svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
2009251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
2010251881Speter
2011251881Speter  if (base_checksum)
2012251881Speter    ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
2013251881Speter
2014251881Speter  return SVN_NO_ERROR;
2015251881Speter}
2016251881Speter
2017251881Speterstatic svn_error_t *
2018251881Speterchange_file_prop(void *file_baton,
2019251881Speter                 const char *name,
2020251881Speter                 const svn_string_t *value,
2021251881Speter                 apr_pool_t *pool)
2022251881Speter{
2023251881Speter  file_context_t *file = file_baton;
2024251881Speter  const char *ns;
2025251881Speter
2026251881Speter  name = apr_pstrdup(file->pool, name);
2027251881Speter
2028251881Speter  if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2029251881Speter    {
2030251881Speter      ns = SVN_DAV_PROP_NS_SVN;
2031251881Speter      name += sizeof(SVN_PROP_PREFIX) - 1;
2032251881Speter    }
2033251881Speter  else
2034251881Speter    {
2035251881Speter      ns = SVN_DAV_PROP_NS_CUSTOM;
2036251881Speter    }
2037251881Speter
2038251881Speter  if (value)
2039251881Speter    {
2040251881Speter      value = svn_string_dup(value, file->pool);
2041251881Speter      svn_ra_serf__set_prop(file->changed_props, file->url,
2042251881Speter                            ns, name, value, file->pool);
2043251881Speter    }
2044251881Speter  else
2045251881Speter    {
2046251881Speter      value = svn_string_create_empty(file->pool);
2047251881Speter
2048251881Speter      svn_ra_serf__set_prop(file->removed_props, file->url,
2049251881Speter                            ns, name, value, file->pool);
2050251881Speter    }
2051251881Speter
2052251881Speter  return SVN_NO_ERROR;
2053251881Speter}
2054251881Speter
2055251881Speterstatic svn_error_t *
2056251881Speterclose_file(void *file_baton,
2057251881Speter           const char *text_checksum,
2058251881Speter           apr_pool_t *scratch_pool)
2059251881Speter{
2060251881Speter  file_context_t *ctx = file_baton;
2061251881Speter  svn_boolean_t put_empty_file = FALSE;
2062251881Speter  apr_status_t status;
2063251881Speter
2064251881Speter  ctx->result_checksum = text_checksum;
2065251881Speter
2066251881Speter  if (ctx->copy_path)
2067251881Speter    {
2068251881Speter      svn_ra_serf__handler_t *handler;
2069251881Speter      apr_uri_t uri;
2070251881Speter      const char *req_url;
2071251881Speter
2072251881Speter      status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
2073251881Speter      if (status)
2074251881Speter        {
2075251881Speter          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2076251881Speter                                   _("Unable to parse URL '%s'"),
2077251881Speter                                   ctx->copy_path);
2078251881Speter        }
2079251881Speter
2080251881Speter      /* ### conn==NULL for session->conns[0]. same as commit->conn.  */
2081251881Speter      SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
2082251881Speter                                          ctx->commit->session,
2083251881Speter                                          NULL /* conn */,
2084251881Speter                                          uri.path, ctx->copy_revision,
2085251881Speter                                          scratch_pool, scratch_pool));
2086251881Speter
2087251881Speter      handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2088251881Speter      handler->handler_pool = scratch_pool;
2089251881Speter      handler->method = "COPY";
2090251881Speter      handler->path = req_url;
2091251881Speter      handler->conn = ctx->commit->conn;
2092251881Speter      handler->session = ctx->commit->session;
2093251881Speter
2094251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
2095251881Speter      handler->response_baton = handler;
2096251881Speter
2097251881Speter      handler->header_delegate = setup_copy_file_headers;
2098251881Speter      handler->header_delegate_baton = ctx;
2099251881Speter
2100251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2101251881Speter
2102251881Speter      if (handler->sline.code != 201 && handler->sline.code != 204)
2103251881Speter        {
2104251881Speter          return svn_error_trace(return_response_err(handler));
2105251881Speter        }
2106251881Speter    }
2107251881Speter
2108251881Speter  /* If we got no stream of changes, but this is an added-without-history
2109251881Speter   * file, make a note that we'll be PUTting a zero-byte file to the server.
2110251881Speter   */
2111251881Speter  if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
2112251881Speter    put_empty_file = TRUE;
2113251881Speter
2114251881Speter  /* If we had a stream of changes, push them to the server... */
2115251881Speter  if (ctx->stream || put_empty_file)
2116251881Speter    {
2117251881Speter      svn_ra_serf__handler_t *handler;
2118251881Speter
2119251881Speter      handler = apr_pcalloc(scratch_pool, sizeof(*handler));
2120251881Speter      handler->handler_pool = scratch_pool;
2121251881Speter      handler->method = "PUT";
2122251881Speter      handler->path = ctx->url;
2123251881Speter      handler->conn = ctx->commit->conn;
2124251881Speter      handler->session = ctx->commit->session;
2125251881Speter
2126251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
2127251881Speter      handler->response_baton = handler;
2128251881Speter
2129251881Speter      if (put_empty_file)
2130251881Speter        {
2131251881Speter          handler->body_delegate = create_empty_put_body;
2132251881Speter          handler->body_delegate_baton = ctx;
2133251881Speter          handler->body_type = "text/plain";
2134251881Speter        }
2135251881Speter      else
2136251881Speter        {
2137251881Speter          handler->body_delegate = create_put_body;
2138251881Speter          handler->body_delegate_baton = ctx;
2139251881Speter          handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2140251881Speter        }
2141251881Speter
2142251881Speter      handler->header_delegate = setup_put_headers;
2143251881Speter      handler->header_delegate_baton = ctx;
2144251881Speter
2145251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2146251881Speter
2147251881Speter      if (handler->sline.code != 204 && handler->sline.code != 201)
2148251881Speter        {
2149251881Speter          return svn_error_trace(return_response_err(handler));
2150251881Speter        }
2151251881Speter    }
2152251881Speter
2153251881Speter  if (ctx->svndiff)
2154251881Speter    SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
2155251881Speter
2156251881Speter  /* If we had any prop changes, push them via PROPPATCH. */
2157251881Speter  if (apr_hash_count(ctx->changed_props) ||
2158251881Speter      apr_hash_count(ctx->removed_props))
2159251881Speter    {
2160251881Speter      proppatch_context_t *proppatch;
2161251881Speter
2162251881Speter      proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
2163251881Speter      proppatch->pool = ctx->pool;
2164251881Speter      proppatch->relpath = ctx->relpath;
2165251881Speter      proppatch->path = ctx->url;
2166251881Speter      proppatch->commit = ctx->commit;
2167251881Speter      proppatch->changed_props = ctx->changed_props;
2168251881Speter      proppatch->removed_props = ctx->removed_props;
2169251881Speter      proppatch->base_revision = ctx->base_revision;
2170251881Speter
2171251881Speter      SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
2172251881Speter    }
2173251881Speter
2174251881Speter  return SVN_NO_ERROR;
2175251881Speter}
2176251881Speter
2177251881Speterstatic svn_error_t *
2178251881Speterclose_edit(void *edit_baton,
2179251881Speter           apr_pool_t *pool)
2180251881Speter{
2181251881Speter  commit_context_t *ctx = edit_baton;
2182251881Speter  const char *merge_target =
2183251881Speter    ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2184251881Speter  const svn_commit_info_t *commit_info;
2185251881Speter  int response_code;
2186251881Speter
2187251881Speter  /* MERGE our activity */
2188251881Speter  SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
2189251881Speter                                 ctx->session,
2190251881Speter                                 ctx->session->conns[0],
2191251881Speter                                 merge_target,
2192251881Speter                                 ctx->lock_tokens,
2193251881Speter                                 ctx->keep_locks,
2194251881Speter                                 pool, pool));
2195251881Speter
2196251881Speter  if (response_code != 200)
2197251881Speter    {
2198251881Speter      return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2199251881Speter                               _("MERGE request failed: returned %d "
2200251881Speter                                 "(during commit)"),
2201251881Speter                               response_code);
2202251881Speter    }
2203251881Speter
2204251881Speter  /* Inform the WC that we did a commit.  */
2205251881Speter  if (ctx->callback)
2206251881Speter    SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
2207251881Speter
2208251881Speter  /* If we're using activities, DELETE our completed activity.  */
2209251881Speter  if (ctx->activity_url)
2210251881Speter    {
2211251881Speter      svn_ra_serf__handler_t *handler;
2212251881Speter
2213251881Speter      handler = apr_pcalloc(pool, sizeof(*handler));
2214251881Speter      handler->handler_pool = pool;
2215251881Speter      handler->method = "DELETE";
2216251881Speter      handler->path = ctx->activity_url;
2217251881Speter      handler->conn = ctx->conn;
2218251881Speter      handler->session = ctx->session;
2219251881Speter
2220251881Speter      handler->response_handler = svn_ra_serf__expect_empty_body;
2221251881Speter      handler->response_baton = handler;
2222251881Speter
2223251881Speter      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2224251881Speter
2225251881Speter      SVN_ERR_ASSERT(handler->sline.code == 204);
2226251881Speter    }
2227251881Speter
2228251881Speter  return SVN_NO_ERROR;
2229251881Speter}
2230251881Speter
2231251881Speterstatic svn_error_t *
2232251881Speterabort_edit(void *edit_baton,
2233251881Speter           apr_pool_t *pool)
2234251881Speter{
2235251881Speter  commit_context_t *ctx = edit_baton;
2236251881Speter  svn_ra_serf__handler_t *handler;
2237251881Speter
2238251881Speter  /* If an activity or transaction wasn't even created, don't bother
2239251881Speter     trying to delete it. */
2240251881Speter  if (! (ctx->activity_url || ctx->txn_url))
2241251881Speter    return SVN_NO_ERROR;
2242251881Speter
2243251881Speter  /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2244251881Speter     had a problem. We need to reset it, in order to use it again.  */
2245251881Speter  serf_connection_reset(ctx->session->conns[0]->conn);
2246251881Speter
2247251881Speter  /* DELETE our aborted activity */
2248251881Speter  handler = apr_pcalloc(pool, sizeof(*handler));
2249251881Speter  handler->handler_pool = pool;
2250251881Speter  handler->method = "DELETE";
2251251881Speter  handler->conn = ctx->session->conns[0];
2252251881Speter  handler->session = ctx->session;
2253251881Speter
2254251881Speter  handler->response_handler = svn_ra_serf__expect_empty_body;
2255251881Speter  handler->response_baton = handler;
2256251881Speter
2257251881Speter  if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2258251881Speter    handler->path = ctx->txn_url;
2259251881Speter  else
2260251881Speter    handler->path = ctx->activity_url;
2261251881Speter
2262251881Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2263251881Speter
2264251881Speter  /* 204 if deleted,
2265251881Speter     403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2266251881Speter     404 if the activity wasn't found. */
2267251881Speter  if (handler->sline.code != 204
2268251881Speter      && handler->sline.code != 403
2269251881Speter      && handler->sline.code != 404
2270251881Speter      )
2271251881Speter    {
2272253734Speter      return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2273253734Speter                               _("DELETE returned unexpected status: %d"),
2274253734Speter                               handler->sline.code);
2275251881Speter    }
2276251881Speter
2277251881Speter  return SVN_NO_ERROR;
2278251881Speter}
2279251881Speter
2280251881Spetersvn_error_t *
2281251881Spetersvn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2282251881Speter                               const svn_delta_editor_t **ret_editor,
2283251881Speter                               void **edit_baton,
2284251881Speter                               apr_hash_t *revprop_table,
2285251881Speter                               svn_commit_callback2_t callback,
2286251881Speter                               void *callback_baton,
2287251881Speter                               apr_hash_t *lock_tokens,
2288251881Speter                               svn_boolean_t keep_locks,
2289251881Speter                               apr_pool_t *pool)
2290251881Speter{
2291251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
2292251881Speter  svn_delta_editor_t *editor;
2293251881Speter  commit_context_t *ctx;
2294251881Speter  const char *repos_root;
2295251881Speter  const char *base_relpath;
2296251881Speter  svn_boolean_t supports_ephemeral_props;
2297251881Speter
2298251881Speter  ctx = apr_pcalloc(pool, sizeof(*ctx));
2299251881Speter
2300251881Speter  ctx->pool = pool;
2301251881Speter
2302251881Speter  ctx->session = session;
2303251881Speter  ctx->conn = session->conns[0];
2304251881Speter
2305251881Speter  ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2306251881Speter
2307251881Speter  /* If the server supports ephemeral properties, add some carrying
2308251881Speter     interesting version information. */
2309251881Speter  SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2310251881Speter                                      SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2311251881Speter                                      pool));
2312251881Speter  if (supports_ephemeral_props)
2313251881Speter    {
2314251881Speter      svn_hash_sets(ctx->revprop_table,
2315251881Speter                    apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2316251881Speter                    svn_string_create(SVN_VER_NUMBER, pool));
2317251881Speter      svn_hash_sets(ctx->revprop_table,
2318251881Speter                    apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2319251881Speter                    svn_string_create(session->useragent, pool));
2320251881Speter    }
2321251881Speter
2322251881Speter  ctx->callback = callback;
2323251881Speter  ctx->callback_baton = callback_baton;
2324251881Speter
2325251881Speter  ctx->lock_tokens = lock_tokens;
2326251881Speter  ctx->keep_locks = keep_locks;
2327251881Speter
2328251881Speter  ctx->deleted_entries = apr_hash_make(ctx->pool);
2329251881Speter
2330251881Speter  editor = svn_delta_default_editor(pool);
2331251881Speter  editor->open_root = open_root;
2332251881Speter  editor->delete_entry = delete_entry;
2333251881Speter  editor->add_directory = add_directory;
2334251881Speter  editor->open_directory = open_directory;
2335251881Speter  editor->change_dir_prop = change_dir_prop;
2336251881Speter  editor->close_directory = close_directory;
2337251881Speter  editor->add_file = add_file;
2338251881Speter  editor->open_file = open_file;
2339251881Speter  editor->apply_textdelta = apply_textdelta;
2340251881Speter  editor->change_file_prop = change_file_prop;
2341251881Speter  editor->close_file = close_file;
2342251881Speter  editor->close_edit = close_edit;
2343251881Speter  editor->abort_edit = abort_edit;
2344251881Speter
2345251881Speter  *ret_editor = editor;
2346251881Speter  *edit_baton = ctx;
2347251881Speter
2348251881Speter  SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2349251881Speter  base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2350251881Speter                                       pool);
2351251881Speter
2352251881Speter  SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2353251881Speter                                   *edit_baton, repos_root, base_relpath,
2354251881Speter                                   session->shim_callbacks, pool, pool));
2355251881Speter
2356251881Speter  return SVN_NO_ERROR;
2357251881Speter}
2358251881Speter
2359251881Spetersvn_error_t *
2360251881Spetersvn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2361251881Speter                             svn_revnum_t rev,
2362251881Speter                             const char *name,
2363251881Speter                             const svn_string_t *const *old_value_p,
2364251881Speter                             const svn_string_t *value,
2365251881Speter                             apr_pool_t *pool)
2366251881Speter{
2367251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
2368251881Speter  proppatch_context_t *proppatch_ctx;
2369251881Speter  commit_context_t *commit;
2370251881Speter  const char *proppatch_target;
2371251881Speter  const char *ns;
2372251881Speter  svn_error_t *err;
2373251881Speter
2374251881Speter  if (old_value_p)
2375251881Speter    {
2376251881Speter      svn_boolean_t capable;
2377251881Speter      SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
2378251881Speter                                          SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2379251881Speter                                          pool));
2380251881Speter
2381251881Speter      /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2382251881Speter      SVN_ERR_ASSERT(capable);
2383251881Speter    }
2384251881Speter
2385251881Speter  commit = apr_pcalloc(pool, sizeof(*commit));
2386251881Speter
2387251881Speter  commit->pool = pool;
2388251881Speter
2389251881Speter  commit->session = session;
2390251881Speter  commit->conn = session->conns[0];
2391251881Speter
2392251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2393251881Speter    {
2394251881Speter      proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2395251881Speter    }
2396251881Speter  else
2397251881Speter    {
2398251881Speter      const char *vcc_url;
2399251881Speter
2400251881Speter      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
2401251881Speter                                        commit->conn, pool));
2402251881Speter
2403251881Speter      SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2404251881Speter                                          commit->conn, vcc_url, rev,
2405251881Speter                                          "href",
2406251881Speter                                          pool, pool));
2407251881Speter    }
2408251881Speter
2409251881Speter  if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2410251881Speter    {
2411251881Speter      ns = SVN_DAV_PROP_NS_SVN;
2412251881Speter      name += sizeof(SVN_PROP_PREFIX) - 1;
2413251881Speter    }
2414251881Speter  else
2415251881Speter    {
2416251881Speter      ns = SVN_DAV_PROP_NS_CUSTOM;
2417251881Speter    }
2418251881Speter
2419251881Speter  /* PROPPATCH our log message and pass it along.  */
2420251881Speter  proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2421251881Speter  proppatch_ctx->pool = pool;
2422251881Speter  proppatch_ctx->commit = commit;
2423251881Speter  proppatch_ctx->path = proppatch_target;
2424251881Speter  proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
2425251881Speter  proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
2426251881Speter  if (old_value_p)
2427251881Speter    {
2428251881Speter      proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
2429251881Speter      proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
2430251881Speter    }
2431251881Speter  proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2432251881Speter
2433251881Speter  if (old_value_p && *old_value_p)
2434251881Speter    {
2435251881Speter      svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
2436251881Speter                            proppatch_ctx->path,
2437251881Speter                            ns, name, *old_value_p, proppatch_ctx->pool);
2438251881Speter    }
2439251881Speter  else if (old_value_p)
2440251881Speter    {
2441251881Speter      svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
2442251881Speter
2443251881Speter      svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
2444251881Speter                            proppatch_ctx->path,
2445251881Speter                            ns, name, dummy_value, proppatch_ctx->pool);
2446251881Speter    }
2447251881Speter
2448251881Speter  if (value)
2449251881Speter    {
2450251881Speter      svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
2451251881Speter                            ns, name, value, proppatch_ctx->pool);
2452251881Speter    }
2453251881Speter  else
2454251881Speter    {
2455251881Speter      value = svn_string_create_empty(proppatch_ctx->pool);
2456251881Speter
2457251881Speter      svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
2458251881Speter                            ns, name, value, proppatch_ctx->pool);
2459251881Speter    }
2460251881Speter
2461251881Speter  err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
2462251881Speter  if (err)
2463251881Speter    return
2464251881Speter      svn_error_create
2465251881Speter      (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
2466251881Speter       _("DAV request failed; it's possible that the repository's "
2467251881Speter         "pre-revprop-change hook either failed or is non-existent"));
2468251881Speter
2469251881Speter  return SVN_NO_ERROR;
2470251881Speter}
2471