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