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