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