commit.c revision 299742
1/* 2 * commit.c : entry point for commit RA functions for ra_serf 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include <apr_uri.h> 25#include <serf.h> 26 27#include "svn_hash.h" 28#include "svn_pools.h" 29#include "svn_ra.h" 30#include "svn_dav.h" 31#include "svn_xml.h" 32#include "svn_config.h" 33#include "svn_delta.h" 34#include "svn_base64.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_props.h" 38 39#include "svn_private_config.h" 40#include "private/svn_dep_compat.h" 41#include "private/svn_fspath.h" 42#include "private/svn_skel.h" 43 44#include "ra_serf.h" 45#include "../libsvn_ra/ra_loader.h" 46 47 48/* Baton passed back with the commit editor. */ 49typedef struct commit_context_t { 50 /* Pool for our commit. */ 51 apr_pool_t *pool; 52 53 svn_ra_serf__session_t *session; 54 55 apr_hash_t *revprop_table; 56 57 svn_commit_callback2_t callback; 58 void *callback_baton; 59 60 apr_hash_t *lock_tokens; 61 svn_boolean_t keep_locks; 62 apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */ 63 64 /* HTTP v2 stuff */ 65 const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */ 66 const char *txn_root_url; /* commit anchor txn root URL */ 67 68 /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */ 69 const char *activity_url; /* activity base URL... */ 70 const char *baseline_url; /* the working-baseline resource */ 71 const char *checked_in_url; /* checked-in root to base CHECKOUTs from */ 72 const char *vcc_url; /* vcc url */ 73 74} commit_context_t; 75 76#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL) 77 78/* Structure associated with a PROPPATCH request. */ 79typedef struct proppatch_context_t { 80 apr_pool_t *pool; 81 82 const char *relpath; 83 const char *path; 84 85 commit_context_t *commit_ctx; 86 87 /* Changed properties. const char * -> svn_prop_t * */ 88 apr_hash_t *prop_changes; 89 90 /* Same, for the old value, or NULL. */ 91 apr_hash_t *old_props; 92 93 /* In HTTP v2, this is the file/directory version we think we're changing. */ 94 svn_revnum_t base_revision; 95 96} proppatch_context_t; 97 98typedef struct delete_context_t { 99 const char *relpath; 100 101 svn_revnum_t revision; 102 103 commit_context_t *commit_ctx; 104 105 svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */ 106} delete_context_t; 107 108/* Represents a directory. */ 109typedef struct dir_context_t { 110 /* Pool for our directory. */ 111 apr_pool_t *pool; 112 113 /* The root commit we're in progress for. */ 114 commit_context_t *commit_ctx; 115 116 /* URL to operate against (used for CHECKOUT and PROPPATCH before 117 HTTP v2, for PROPPATCH in HTTP v2). */ 118 const char *url; 119 120 /* How many pending changes we have left in this directory. */ 121 unsigned int ref_count; 122 123 /* Is this directory being added? (Otherwise, just opened.) */ 124 svn_boolean_t added; 125 126 /* Our parent */ 127 struct dir_context_t *parent_dir; 128 129 /* The directory name; if "", we're the 'root' */ 130 const char *relpath; 131 132 /* The basename of the directory. "" for the 'root' */ 133 const char *name; 134 135 /* The base revision of the dir. */ 136 svn_revnum_t base_revision; 137 138 const char *copy_path; 139 svn_revnum_t copy_revision; 140 141 /* Changed properties (const char * -> svn_prop_t *) */ 142 apr_hash_t *prop_changes; 143 144 /* The checked-out working resource for this directory. May be NULL; if so 145 call checkout_dir() first. */ 146 const char *working_url; 147} dir_context_t; 148 149/* Represents a file to be committed. */ 150typedef struct file_context_t { 151 /* Pool for our file. */ 152 apr_pool_t *pool; 153 154 /* The root commit we're in progress for. */ 155 commit_context_t *commit_ctx; 156 157 /* Is this file being added? (Otherwise, just opened.) */ 158 svn_boolean_t added; 159 160 dir_context_t *parent_dir; 161 162 const char *relpath; 163 const char *name; 164 165 /* The checked-out working resource for this file. */ 166 const char *working_url; 167 168 /* The base revision of the file. */ 169 svn_revnum_t base_revision; 170 171 /* Copy path and revision */ 172 const char *copy_path; 173 svn_revnum_t copy_revision; 174 175 /* stream */ 176 svn_stream_t *stream; 177 178 /* Temporary file containing the svndiff. */ 179 apr_file_t *svndiff; 180 181 /* Our base checksum as reported by the WC. */ 182 const char *base_checksum; 183 184 /* Our resulting checksum as reported by the WC. */ 185 const char *result_checksum; 186 187 /* Changed properties (const char * -> svn_prop_t *) */ 188 apr_hash_t *prop_changes; 189 190 /* URL to PUT the file at. */ 191 const char *url; 192 193} file_context_t; 194 195 196/* Setup routines and handlers for various requests we'll invoke. */ 197 198/* Implements svn_ra_serf__request_body_delegate_t */ 199static svn_error_t * 200create_checkout_body(serf_bucket_t **bkt, 201 void *baton, 202 serf_bucket_alloc_t *alloc, 203 apr_pool_t *pool /* request pool */, 204 apr_pool_t *scratch_pool) 205{ 206 const char *activity_url = baton; 207 serf_bucket_t *body_bkt; 208 209 body_bkt = serf_bucket_aggregate_create(alloc); 210 211 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); 212 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout", 213 "xmlns:D", "DAV:", 214 SVN_VA_NULL); 215 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", 216 SVN_VA_NULL); 217 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", 218 SVN_VA_NULL); 219 220 SVN_ERR_ASSERT(activity_url != NULL); 221 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc, 222 activity_url, 223 strlen(activity_url)); 224 225 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href"); 226 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set"); 227 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 228 "D:apply-to-version", SVN_VA_NULL); 229 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout"); 230 231 *bkt = body_bkt; 232 return SVN_NO_ERROR; 233} 234 235 236/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the 237 given COMMIT_CTX. The resulting working resource will be returned in 238 *WORKING_URL, allocated from RESULT_POOL. All temporary allocations 239 are performed in SCRATCH_POOL. 240 241 ### are these URLs actually repos relpath values? or fspath? or maybe 242 ### the abspath portion of the full URL. 243 244 This function operates synchronously. 245 246 Strictly speaking, we could perform "all" of the CHECKOUT requests 247 when the commit starts, and only block when we need a specific 248 answer. Or, at a minimum, send off these individual requests async 249 and block when we need the answer (eg PUT or PROPPATCH). 250 251 However: the investment to speed this up is not worthwhile, given 252 that CHECKOUT (and the related round trip) is completely obviated 253 in HTTPv2. 254*/ 255static svn_error_t * 256checkout_node(const char **working_url, 257 const commit_context_t *commit_ctx, 258 const char *node_url, 259 apr_pool_t *result_pool, 260 apr_pool_t *scratch_pool) 261{ 262 svn_ra_serf__handler_t *handler; 263 apr_status_t status; 264 apr_uri_t uri; 265 266 /* HANDLER_POOL is the scratch pool since we don't need to remember 267 anything from the handler. We just want the working resource. */ 268 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool); 269 270 handler->body_delegate = create_checkout_body; 271 handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url; 272 handler->body_type = "text/xml"; 273 274 handler->response_handler = svn_ra_serf__expect_empty_body; 275 handler->response_baton = handler; 276 277 handler->method = "CHECKOUT"; 278 handler->path = node_url; 279 280 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 281 282 if (handler->sline.code != 201) 283 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 284 285 if (handler->location == NULL) 286 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 287 _("No Location header received")); 288 289 /* We only want the path portion of the Location header. 290 (code.google.com sometimes returns an 'http:' scheme for an 291 'https:' transaction ... we'll work around that by stripping the 292 scheme, host, and port here and re-adding the correct ones 293 later. */ 294 status = apr_uri_parse(scratch_pool, handler->location, &uri); 295 if (status) 296 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 297 _("Error parsing Location header value")); 298 299 *working_url = svn_urlpath__canonicalize(uri.path, result_pool); 300 301 return SVN_NO_ERROR; 302} 303 304 305/* This is a wrapper around checkout_node() (which see for 306 documentation) which simply retries the CHECKOUT request when it 307 fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the 308 server. 309 310 See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for 311 details. 312*/ 313static svn_error_t * 314retry_checkout_node(const char **working_url, 315 const commit_context_t *commit_ctx, 316 const char *node_url, 317 apr_pool_t *result_pool, 318 apr_pool_t *scratch_pool) 319{ 320 svn_error_t *err = SVN_NO_ERROR; 321 int retry_count = 5; /* Magic, arbitrary number. */ 322 323 do 324 { 325 svn_error_clear(err); 326 327 err = checkout_node(working_url, commit_ctx, node_url, 328 result_pool, scratch_pool); 329 330 /* There's a small chance of a race condition here if Apache is 331 experiencing heavy commit concurrency or if the network has 332 long latency. It's possible that the value of HEAD changed 333 between the time we fetched the latest baseline and the time 334 we try to CHECKOUT that baseline. If that happens, Apache 335 will throw us a BAD_BASELINE error (deltaV says you can only 336 checkout the latest baseline). We just ignore that specific 337 error and retry a few times, asking for the latest baseline 338 again. */ 339 if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE)) 340 return svn_error_trace(err); 341 } 342 while (err && retry_count--); 343 344 return svn_error_trace(err); 345} 346 347 348static svn_error_t * 349checkout_dir(dir_context_t *dir, 350 apr_pool_t *scratch_pool) 351{ 352 dir_context_t *c_dir = dir; 353 const char *checkout_url; 354 const char **working; 355 356 if (dir->working_url) 357 { 358 return SVN_NO_ERROR; 359 } 360 361 /* Is this directory or one of our parent dirs newly added? 362 * If so, we're already implicitly checked out. */ 363 while (c_dir) 364 { 365 if (c_dir->added) 366 { 367 /* Calculate the working_url by skipping the shared ancestor between 368 * the c_dir_parent->relpath and dir->relpath. This is safe since an 369 * add is guaranteed to have a parent that is checked out. */ 370 dir_context_t *c_dir_parent = c_dir->parent_dir; 371 const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath, 372 dir->relpath); 373 374 /* Implicitly checkout this dir now. */ 375 SVN_ERR_ASSERT(c_dir_parent->working_url); 376 dir->working_url = svn_path_url_add_component2( 377 c_dir_parent->working_url, 378 relpath, dir->pool); 379 return SVN_NO_ERROR; 380 } 381 c_dir = c_dir->parent_dir; 382 } 383 384 /* We could be called twice for the root: once to checkout the baseline; 385 * once to checkout the directory itself if we need to do so. 386 * Note: CHECKOUT_URL should live longer than HANDLER. 387 */ 388 if (!dir->parent_dir && !dir->commit_ctx->baseline_url) 389 { 390 checkout_url = dir->commit_ctx->vcc_url; 391 working = &dir->commit_ctx->baseline_url; 392 } 393 else 394 { 395 checkout_url = dir->url; 396 working = &dir->working_url; 397 } 398 399 /* Checkout our directory into the activity URL now. */ 400 return svn_error_trace(retry_checkout_node(working, dir->commit_ctx, 401 checkout_url, 402 dir->pool, scratch_pool)); 403} 404 405 406/* Set *CHECKED_IN_URL to the appropriate DAV version url for 407 * RELPATH (relative to the root of SESSION). 408 * 409 * Try to find this version url in three ways: 410 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the 411 * version url from the working copy properties. 412 * Second, if the version url of the parent directory PARENT_VSN_URL is 413 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with 414 * RELPATH. 415 * Else, fetch the version url for the root of SESSION using CONN and 416 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that 417 * with RELPATH. 418 * 419 * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for 420 * temporary allocation. 421 */ 422static svn_error_t * 423get_version_url(const char **checked_in_url, 424 svn_ra_serf__session_t *session, 425 const char *relpath, 426 svn_revnum_t base_revision, 427 const char *parent_vsn_url, 428 apr_pool_t *result_pool, 429 apr_pool_t *scratch_pool) 430{ 431 const char *root_checkout; 432 433 if (session->wc_callbacks->get_wc_prop) 434 { 435 const svn_string_t *current_version; 436 437 SVN_ERR(session->wc_callbacks->get_wc_prop( 438 session->wc_callback_baton, 439 relpath, 440 SVN_RA_SERF__WC_CHECKED_IN_URL, 441 ¤t_version, scratch_pool)); 442 443 if (current_version) 444 { 445 *checked_in_url = 446 svn_urlpath__canonicalize(current_version->data, result_pool); 447 return SVN_NO_ERROR; 448 } 449 } 450 451 if (parent_vsn_url) 452 { 453 root_checkout = parent_vsn_url; 454 } 455 else 456 { 457 const char *propfind_url; 458 459 if (SVN_IS_VALID_REVNUM(base_revision)) 460 { 461 /* mod_dav_svn can't handle the "Label:" header that 462 svn_ra_serf__deliver_props() is going to try to use for 463 this lookup, so we'll do things the hard(er) way, by 464 looking up the version URL from a resource in the 465 baseline collection. */ 466 SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url, 467 NULL /* latest_revnum */, 468 session, 469 NULL /* url */, base_revision, 470 scratch_pool, scratch_pool)); 471 } 472 else 473 { 474 propfind_url = session->session_url.path; 475 } 476 477 SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session, 478 propfind_url, base_revision, 479 "checked-in", 480 scratch_pool, scratch_pool)); 481 if (!root_checkout) 482 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 483 _("Path '%s' not present"), 484 session->session_url.path); 485 486 root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool); 487 } 488 489 *checked_in_url = svn_path_url_add_component2(root_checkout, relpath, 490 result_pool); 491 492 return SVN_NO_ERROR; 493} 494 495static svn_error_t * 496checkout_file(file_context_t *file, 497 apr_pool_t *scratch_pool) 498{ 499 dir_context_t *parent_dir = file->parent_dir; 500 const char *checkout_url; 501 502 /* Is one of our parent dirs newly added? If so, we're already 503 * implicitly checked out. 504 */ 505 while (parent_dir) 506 { 507 if (parent_dir->added) 508 { 509 /* Implicitly checkout this file now. */ 510 SVN_ERR_ASSERT(parent_dir->working_url); 511 file->working_url = svn_path_url_add_component2( 512 parent_dir->working_url, 513 svn_relpath_skip_ancestor( 514 parent_dir->relpath, file->relpath), 515 file->pool); 516 return SVN_NO_ERROR; 517 } 518 parent_dir = parent_dir->parent_dir; 519 } 520 521 SVN_ERR(get_version_url(&checkout_url, 522 file->commit_ctx->session, 523 file->relpath, file->base_revision, 524 NULL, scratch_pool, scratch_pool)); 525 526 /* Checkout our file into the activity URL now. */ 527 return svn_error_trace(retry_checkout_node(&file->working_url, 528 file->commit_ctx, checkout_url, 529 file->pool, scratch_pool)); 530} 531 532/* Helper function for proppatch_walker() below. */ 533static svn_error_t * 534get_encoding_and_cdata(const char **encoding_p, 535 const svn_string_t **encoded_value_p, 536 serf_bucket_alloc_t *alloc, 537 const svn_string_t *value, 538 apr_pool_t *result_pool, 539 apr_pool_t *scratch_pool) 540{ 541 if (value == NULL) 542 { 543 *encoding_p = NULL; 544 *encoded_value_p = NULL; 545 return SVN_NO_ERROR; 546 } 547 548 /* If a property is XML-safe, XML-encode it. Else, base64-encode 549 it. */ 550 if (svn_xml_is_xml_safe(value->data, value->len)) 551 { 552 svn_stringbuf_t *xml_esc = NULL; 553 svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool); 554 *encoding_p = NULL; 555 *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool); 556 } 557 else 558 { 559 *encoding_p = "base64"; 560 *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool); 561 } 562 563 return SVN_NO_ERROR; 564} 565 566/* Helper for create_proppatch_body. Writes per property xml to body */ 567static svn_error_t * 568write_prop_xml(const proppatch_context_t *proppatch, 569 serf_bucket_t *body_bkt, 570 serf_bucket_alloc_t *alloc, 571 const svn_prop_t *prop, 572 apr_pool_t *result_pool, 573 apr_pool_t *scratch_pool) 574{ 575 serf_bucket_t *cdata_bkt; 576 const char *encoding; 577 const svn_string_t *encoded_value; 578 const char *prop_name; 579 const svn_prop_t *old_prop; 580 581 SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value, 582 result_pool, scratch_pool)); 583 if (encoded_value) 584 { 585 cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data, 586 encoded_value->len, 587 alloc); 588 } 589 else 590 { 591 cdata_bkt = NULL; 592 } 593 594 /* Use the namespace prefix instead of adding the xmlns attribute to support 595 property names containing ':' */ 596 if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0) 597 { 598 prop_name = apr_pstrcat(result_pool, 599 "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1, 600 SVN_VA_NULL); 601 } 602 else 603 { 604 prop_name = apr_pstrcat(result_pool, 605 "C:", prop->name, 606 SVN_VA_NULL); 607 } 608 609 if (cdata_bkt) 610 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name, 611 "V:encoding", encoding, 612 SVN_VA_NULL); 613 else 614 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name, 615 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1", 616 SVN_VA_NULL); 617 618 old_prop = proppatch->old_props 619 ? svn_hash_gets(proppatch->old_props, prop->name) 620 : NULL; 621 if (old_prop) 622 { 623 const char *encoding2; 624 const svn_string_t *encoded_value2; 625 serf_bucket_t *cdata_bkt2; 626 627 SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2, 628 alloc, old_prop->value, 629 result_pool, scratch_pool)); 630 631 if (encoded_value2) 632 { 633 cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data, 634 encoded_value2->len, 635 alloc); 636 } 637 else 638 { 639 cdata_bkt2 = NULL; 640 } 641 642 if (cdata_bkt2) 643 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, 644 "V:" SVN_DAV__OLD_VALUE, 645 "V:encoding", encoding2, 646 SVN_VA_NULL); 647 else 648 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, 649 "V:" SVN_DAV__OLD_VALUE, 650 "V:" SVN_DAV__OLD_VALUE__ABSENT, "1", 651 SVN_VA_NULL); 652 653 if (cdata_bkt2) 654 serf_bucket_aggregate_append(body_bkt, cdata_bkt2); 655 656 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, 657 "V:" SVN_DAV__OLD_VALUE); 658 } 659 if (cdata_bkt) 660 serf_bucket_aggregate_append(body_bkt, cdata_bkt); 661 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name); 662 663 return SVN_NO_ERROR; 664} 665 666/* Possible add the lock-token "If:" precondition header to HEADERS if 667 an examination of COMMIT_CTX and RELPATH indicates that this is the 668 right thing to do. 669 670 Generally speaking, if the client provided a lock token for 671 RELPATH, it's the right thing to do. There is a notable instance 672 where this is not the case, however. If the file at RELPATH was 673 explicitly deleted in this commit already, then mod_dav removed its 674 lock token when it fielded the DELETE request, so we don't want to 675 set the lock precondition again. (See 676 http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.) 677*/ 678static svn_error_t * 679maybe_set_lock_token_header(serf_bucket_t *headers, 680 commit_context_t *commit_ctx, 681 const char *relpath, 682 apr_pool_t *pool) 683{ 684 const char *token; 685 686 if (! (*relpath && commit_ctx->lock_tokens)) 687 return SVN_NO_ERROR; 688 689 if (! svn_hash_gets(commit_ctx->deleted_entries, relpath)) 690 { 691 token = svn_hash_gets(commit_ctx->lock_tokens, relpath); 692 if (token) 693 { 694 const char *token_header; 695 const char *token_uri; 696 apr_uri_t uri = commit_ctx->session->session_url; 697 698 /* Supplying the optional URI affects apache response when 699 the lock is broken, see issue 4369. When present any URI 700 must be absolute (RFC 2518 9.4). */ 701 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath, 702 pool); 703 token_uri = apr_uri_unparse(pool, &uri, 0); 704 705 token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)", 706 SVN_VA_NULL); 707 serf_bucket_headers_set(headers, "If", token_header); 708 } 709 } 710 711 return SVN_NO_ERROR; 712} 713 714static svn_error_t * 715setup_proppatch_headers(serf_bucket_t *headers, 716 void *baton, 717 apr_pool_t *pool /* request pool */, 718 apr_pool_t *scratch_pool) 719{ 720 proppatch_context_t *proppatch = baton; 721 722 if (SVN_IS_VALID_REVNUM(proppatch->base_revision)) 723 { 724 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, 725 apr_psprintf(pool, "%ld", 726 proppatch->base_revision)); 727 } 728 729 if (proppatch->relpath && proppatch->commit_ctx) 730 SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx, 731 proppatch->relpath, pool)); 732 733 return SVN_NO_ERROR; 734} 735 736 737/* Implements svn_ra_serf__request_body_delegate_t */ 738static svn_error_t * 739create_proppatch_body(serf_bucket_t **bkt, 740 void *baton, 741 serf_bucket_alloc_t *alloc, 742 apr_pool_t *pool /* request pool */, 743 apr_pool_t *scratch_pool) 744{ 745 proppatch_context_t *ctx = baton; 746 serf_bucket_t *body_bkt; 747 svn_boolean_t opened = FALSE; 748 apr_hash_index_t *hi; 749 750 body_bkt = serf_bucket_aggregate_create(alloc); 751 752 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); 753 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate", 754 "xmlns:D", "DAV:", 755 "xmlns:V", SVN_DAV_PROP_NS_DAV, 756 "xmlns:C", SVN_DAV_PROP_NS_CUSTOM, 757 "xmlns:S", SVN_DAV_PROP_NS_SVN, 758 SVN_VA_NULL); 759 760 /* First we write property SETs */ 761 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes); 762 hi; 763 hi = apr_hash_next(hi)) 764 { 765 svn_prop_t *prop = apr_hash_this_val(hi); 766 767 if (prop->value 768 || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name))) 769 { 770 if (!opened) 771 { 772 opened = TRUE; 773 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", 774 SVN_VA_NULL); 775 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", 776 SVN_VA_NULL); 777 } 778 779 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop, 780 pool, scratch_pool)); 781 } 782 } 783 784 if (opened) 785 { 786 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); 787 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set"); 788 } 789 790 /* And then property REMOVEs */ 791 opened = FALSE; 792 793 for (hi = apr_hash_first(scratch_pool, ctx->prop_changes); 794 hi; 795 hi = apr_hash_next(hi)) 796 { 797 svn_prop_t *prop = apr_hash_this_val(hi); 798 799 if (!prop->value 800 && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name))) 801 { 802 if (!opened) 803 { 804 opened = TRUE; 805 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", 806 SVN_VA_NULL); 807 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", 808 SVN_VA_NULL); 809 } 810 811 SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop, 812 pool, scratch_pool)); 813 } 814 } 815 816 if (opened) 817 { 818 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); 819 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove"); 820 } 821 822 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate"); 823 824 *bkt = body_bkt; 825 return SVN_NO_ERROR; 826} 827 828static svn_error_t* 829proppatch_resource(svn_ra_serf__session_t *session, 830 proppatch_context_t *proppatch, 831 apr_pool_t *pool) 832{ 833 svn_ra_serf__handler_t *handler; 834 svn_error_t *err; 835 836 handler = svn_ra_serf__create_handler(session, pool); 837 838 handler->method = "PROPPATCH"; 839 handler->path = proppatch->path; 840 841 handler->header_delegate = setup_proppatch_headers; 842 handler->header_delegate_baton = proppatch; 843 844 handler->body_delegate = create_proppatch_body; 845 handler->body_delegate_baton = proppatch; 846 handler->body_type = "text/xml"; 847 848 handler->response_handler = svn_ra_serf__handle_multistatus_only; 849 handler->response_baton = handler; 850 851 err = svn_ra_serf__context_run_one(handler, pool); 852 853 if (!err && handler->sline.code != 207) 854 err = svn_error_trace(svn_ra_serf__unexpected_status(handler)); 855 856 /* Use specific error code for property handling errors. 857 Use loop to provide the right result with tracing */ 858 if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) 859 { 860 svn_error_t *e = err; 861 862 while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) 863 { 864 e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED; 865 e = e->child; 866 } 867 } 868 869 return svn_error_trace(err); 870} 871 872/* Implements svn_ra_serf__request_body_delegate_t */ 873static svn_error_t * 874create_put_body(serf_bucket_t **body_bkt, 875 void *baton, 876 serf_bucket_alloc_t *alloc, 877 apr_pool_t *pool /* request pool */, 878 apr_pool_t *scratch_pool) 879{ 880 file_context_t *ctx = baton; 881 apr_off_t offset; 882 883 /* We need to flush the file, make it unbuffered (so that it can be 884 * zero-copied via mmap), and reset the position before attempting to 885 * deliver the file. 886 * 887 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap 888 * and zero-copy the PUT body. However, on older APR versions, we can't 889 * check the buffer status; but serf will fall through and create a file 890 * bucket for us on the buffered svndiff handle. 891 */ 892 SVN_ERR(svn_io_file_flush(ctx->svndiff, pool)); 893 apr_file_buffer_set(ctx->svndiff, NULL, 0); 894 offset = 0; 895 SVN_ERR(svn_io_file_seek(ctx->svndiff, APR_SET, &offset, pool)); 896 897 *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc); 898 return SVN_NO_ERROR; 899} 900 901/* Implements svn_ra_serf__request_body_delegate_t */ 902static svn_error_t * 903create_empty_put_body(serf_bucket_t **body_bkt, 904 void *baton, 905 serf_bucket_alloc_t *alloc, 906 apr_pool_t *pool /* request pool */, 907 apr_pool_t *scratch_pool) 908{ 909 *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc); 910 return SVN_NO_ERROR; 911} 912 913static svn_error_t * 914setup_put_headers(serf_bucket_t *headers, 915 void *baton, 916 apr_pool_t *pool /* request pool */, 917 apr_pool_t *scratch_pool) 918{ 919 file_context_t *ctx = baton; 920 921 if (SVN_IS_VALID_REVNUM(ctx->base_revision)) 922 { 923 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, 924 apr_psprintf(pool, "%ld", ctx->base_revision)); 925 } 926 927 if (ctx->base_checksum) 928 { 929 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER, 930 ctx->base_checksum); 931 } 932 933 if (ctx->result_checksum) 934 { 935 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER, 936 ctx->result_checksum); 937 } 938 939 SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx, 940 ctx->relpath, pool)); 941 942 return APR_SUCCESS; 943} 944 945static svn_error_t * 946setup_copy_file_headers(serf_bucket_t *headers, 947 void *baton, 948 apr_pool_t *pool /* request pool */, 949 apr_pool_t *scratch_pool) 950{ 951 file_context_t *file = baton; 952 apr_uri_t uri; 953 const char *absolute_uri; 954 955 /* The Dest URI must be absolute. Bummer. */ 956 uri = file->commit_ctx->session->session_url; 957 uri.path = (char*)file->url; 958 absolute_uri = apr_uri_unparse(pool, &uri, 0); 959 960 serf_bucket_headers_set(headers, "Destination", absolute_uri); 961 962 serf_bucket_headers_setn(headers, "Overwrite", "F"); 963 964 return SVN_NO_ERROR; 965} 966 967static svn_error_t * 968setup_if_header_recursive(svn_boolean_t *added, 969 serf_bucket_t *headers, 970 commit_context_t *commit_ctx, 971 const char *rq_relpath, 972 apr_pool_t *pool) 973{ 974 svn_stringbuf_t *sb = NULL; 975 apr_hash_index_t *hi; 976 apr_pool_t *iterpool = NULL; 977 978 if (!commit_ctx->lock_tokens) 979 { 980 *added = FALSE; 981 return SVN_NO_ERROR; 982 } 983 984 /* We try to create a directory, so within the Subversion world that 985 would imply that there is nothing here, but mod_dav_svn still sees 986 locks on the old nodes here as in DAV it is perfectly legal to lock 987 something that is not there... 988 989 Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing 990 the locks we know of with the request */ 991 992 for (hi = apr_hash_first(pool, commit_ctx->lock_tokens); 993 hi; 994 hi = apr_hash_next(hi)) 995 { 996 const char *relpath = apr_hash_this_key(hi); 997 apr_uri_t uri; 998 999 if (!svn_relpath_skip_ancestor(rq_relpath, relpath)) 1000 continue; 1001 else if (svn_hash_gets(commit_ctx->deleted_entries, relpath)) 1002 { 1003 /* When a path is already explicit deleted then its lock 1004 will be removed by mod_dav. But mod_dav doesn't remove 1005 locks on descendants */ 1006 continue; 1007 } 1008 1009 if (!iterpool) 1010 iterpool = svn_pool_create(pool); 1011 else 1012 svn_pool_clear(iterpool); 1013 1014 if (sb == NULL) 1015 sb = svn_stringbuf_create("", pool); 1016 else 1017 svn_stringbuf_appendbyte(sb, ' '); 1018 1019 uri = commit_ctx->session->session_url; 1020 uri.path = (char *)svn_path_url_add_component2(uri.path, relpath, 1021 iterpool); 1022 1023 svn_stringbuf_appendbyte(sb, '<'); 1024 svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0)); 1025 svn_stringbuf_appendcstr(sb, "> (<"); 1026 svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi)); 1027 svn_stringbuf_appendcstr(sb, ">)"); 1028 } 1029 1030 if (iterpool) 1031 svn_pool_destroy(iterpool); 1032 1033 if (sb) 1034 { 1035 serf_bucket_headers_set(headers, "If", sb->data); 1036 *added = TRUE; 1037 } 1038 else 1039 *added = FALSE; 1040 1041 return SVN_NO_ERROR; 1042} 1043 1044static svn_error_t * 1045setup_add_dir_common_headers(serf_bucket_t *headers, 1046 void *baton, 1047 apr_pool_t *pool /* request pool */, 1048 apr_pool_t *scratch_pool) 1049{ 1050 dir_context_t *dir = baton; 1051 svn_boolean_t added; 1052 1053 return svn_error_trace( 1054 setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath, 1055 pool)); 1056} 1057 1058static svn_error_t * 1059setup_copy_dir_headers(serf_bucket_t *headers, 1060 void *baton, 1061 apr_pool_t *pool /* request pool */, 1062 apr_pool_t *scratch_pool) 1063{ 1064 dir_context_t *dir = baton; 1065 apr_uri_t uri; 1066 const char *absolute_uri; 1067 1068 /* The Dest URI must be absolute. Bummer. */ 1069 uri = dir->commit_ctx->session->session_url; 1070 1071 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1072 { 1073 uri.path = (char *)dir->url; 1074 } 1075 else 1076 { 1077 uri.path = (char *)svn_path_url_add_component2( 1078 dir->parent_dir->working_url, 1079 dir->name, pool); 1080 } 1081 absolute_uri = apr_uri_unparse(pool, &uri, 0); 1082 1083 serf_bucket_headers_set(headers, "Destination", absolute_uri); 1084 1085 serf_bucket_headers_setn(headers, "Depth", "infinity"); 1086 serf_bucket_headers_setn(headers, "Overwrite", "F"); 1087 1088 /* Implicitly checkout this dir now. */ 1089 dir->working_url = apr_pstrdup(dir->pool, uri.path); 1090 1091 return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool, 1092 scratch_pool)); 1093} 1094 1095static svn_error_t * 1096setup_delete_headers(serf_bucket_t *headers, 1097 void *baton, 1098 apr_pool_t *pool /* request pool */, 1099 apr_pool_t *scratch_pool) 1100{ 1101 delete_context_t *del = baton; 1102 svn_boolean_t added; 1103 1104 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, 1105 apr_ltoa(pool, del->revision)); 1106 1107 if (! del->non_recursive_if) 1108 SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx, 1109 del->relpath, pool)); 1110 else 1111 { 1112 SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx, 1113 del->relpath, pool)); 1114 added = TRUE; 1115 } 1116 1117 if (added && del->commit_ctx->keep_locks) 1118 serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER, 1119 SVN_DAV_OPTION_KEEP_LOCKS); 1120 1121 return SVN_NO_ERROR; 1122} 1123 1124/* POST against 'me' resource handlers. */ 1125 1126/* Implements svn_ra_serf__request_body_delegate_t */ 1127static svn_error_t * 1128create_txn_post_body(serf_bucket_t **body_bkt, 1129 void *baton, 1130 serf_bucket_alloc_t *alloc, 1131 apr_pool_t *pool /* request pool */, 1132 apr_pool_t *scratch_pool) 1133{ 1134 apr_hash_t *revprops = baton; 1135 svn_skel_t *request_skel; 1136 svn_stringbuf_t *skel_str; 1137 1138 request_skel = svn_skel__make_empty_list(pool); 1139 if (revprops) 1140 { 1141 svn_skel_t *proplist_skel; 1142 1143 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool)); 1144 svn_skel__prepend(proplist_skel, request_skel); 1145 svn_skel__prepend_str("create-txn-with-props", request_skel, pool); 1146 skel_str = svn_skel__unparse(request_skel, pool); 1147 *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc); 1148 } 1149 else 1150 { 1151 *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc); 1152 } 1153 1154 return SVN_NO_ERROR; 1155} 1156 1157/* Implements svn_ra_serf__request_header_delegate_t */ 1158static svn_error_t * 1159setup_post_headers(serf_bucket_t *headers, 1160 void *baton, 1161 apr_pool_t *pool /* request pool */, 1162 apr_pool_t *scratch_pool) 1163{ 1164#ifdef SVN_DAV_SEND_VTXN_NAME 1165 /* Enable this to exercise the VTXN-NAME code based on a client 1166 supplied transaction name. */ 1167 serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER, 1168 svn_uuid_generate(pool)); 1169#endif 1170 1171 return SVN_NO_ERROR; 1172} 1173 1174 1175/* Handler baton for POST request. */ 1176typedef struct post_response_ctx_t 1177{ 1178 svn_ra_serf__handler_t *handler; 1179 commit_context_t *commit_ctx; 1180} post_response_ctx_t; 1181 1182 1183/* This implements serf_bucket_headers_do_callback_fn_t. */ 1184static int 1185post_headers_iterator_callback(void *baton, 1186 const char *key, 1187 const char *val) 1188{ 1189 post_response_ctx_t *prc = baton; 1190 commit_context_t *prc_cc = prc->commit_ctx; 1191 svn_ra_serf__session_t *sess = prc_cc->session; 1192 1193 /* If we provided a UUID to the POST request, we should get back 1194 from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we 1195 expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to 1196 see both. */ 1197 1198 if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0) 1199 { 1200 /* Build out txn and txn-root URLs using the txn name we're 1201 given, and store the whole lot of it in the commit context. */ 1202 prc_cc->txn_url = 1203 svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool); 1204 prc_cc->txn_root_url = 1205 svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool); 1206 } 1207 1208 if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0) 1209 { 1210 /* Build out vtxn and vtxn-root URLs using the vtxn name we're 1211 given, and store the whole lot of it in the commit context. */ 1212 prc_cc->txn_url = 1213 svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool); 1214 prc_cc->txn_root_url = 1215 svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool); 1216 } 1217 1218 return 0; 1219} 1220 1221 1222/* A custom serf_response_handler_t which is mostly a wrapper around 1223 svn_ra_serf__expect_empty_body -- it just notices POST response 1224 headers, too. 1225 1226 Implements svn_ra_serf__response_handler_t */ 1227static svn_error_t * 1228post_response_handler(serf_request_t *request, 1229 serf_bucket_t *response, 1230 void *baton, 1231 apr_pool_t *scratch_pool) 1232{ 1233 post_response_ctx_t *prc = baton; 1234 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response); 1235 1236 /* Then see which ones we can discover. */ 1237 serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc); 1238 1239 /* Execute the 'real' response handler to XML-parse the repsonse body. */ 1240 return svn_ra_serf__expect_empty_body(request, response, 1241 prc->handler, scratch_pool); 1242} 1243 1244 1245 1246/* Commit baton callbacks */ 1247 1248static svn_error_t * 1249open_root(void *edit_baton, 1250 svn_revnum_t base_revision, 1251 apr_pool_t *dir_pool, 1252 void **root_baton) 1253{ 1254 commit_context_t *commit_ctx = edit_baton; 1255 svn_ra_serf__handler_t *handler; 1256 proppatch_context_t *proppatch_ctx; 1257 dir_context_t *dir; 1258 apr_hash_index_t *hi; 1259 const char *proppatch_target = NULL; 1260 apr_pool_t *scratch_pool = svn_pool_create(dir_pool); 1261 1262 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session)) 1263 { 1264 post_response_ctx_t *prc; 1265 const char *rel_path; 1266 svn_boolean_t post_with_revprops 1267 = (NULL != svn_hash_gets(commit_ctx->session->supported_posts, 1268 "create-txn-with-props")); 1269 1270 /* Create our activity URL now on the server. */ 1271 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool); 1272 1273 handler->method = "POST"; 1274 handler->body_type = SVN_SKEL_MIME_TYPE; 1275 handler->body_delegate = create_txn_post_body; 1276 handler->body_delegate_baton = 1277 post_with_revprops ? commit_ctx->revprop_table : NULL; 1278 handler->header_delegate = setup_post_headers; 1279 handler->header_delegate_baton = NULL; 1280 handler->path = commit_ctx->session->me_resource; 1281 1282 prc = apr_pcalloc(scratch_pool, sizeof(*prc)); 1283 prc->handler = handler; 1284 prc->commit_ctx = commit_ctx; 1285 1286 handler->response_handler = post_response_handler; 1287 handler->response_baton = prc; 1288 1289 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 1290 1291 if (handler->sline.code != 201) 1292 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1293 1294 if (! (commit_ctx->txn_root_url && commit_ctx->txn_url)) 1295 { 1296 return svn_error_createf( 1297 SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1298 _("POST request did not return transaction information")); 1299 } 1300 1301 /* Fixup the txn_root_url to point to the anchor of the commit. */ 1302 SVN_ERR(svn_ra_serf__get_relative_path( 1303 &rel_path, 1304 commit_ctx->session->session_url.path, 1305 commit_ctx->session, 1306 scratch_pool)); 1307 commit_ctx->txn_root_url = svn_path_url_add_component2( 1308 commit_ctx->txn_root_url, 1309 rel_path, commit_ctx->pool); 1310 1311 /* Build our directory baton. */ 1312 dir = apr_pcalloc(dir_pool, sizeof(*dir)); 1313 dir->pool = dir_pool; 1314 dir->commit_ctx = commit_ctx; 1315 dir->base_revision = base_revision; 1316 dir->relpath = ""; 1317 dir->name = ""; 1318 dir->prop_changes = apr_hash_make(dir->pool); 1319 dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url); 1320 1321 /* If we included our revprops in the POST, we need not 1322 PROPPATCH them. */ 1323 proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url; 1324 } 1325 else 1326 { 1327 const char *activity_str = commit_ctx->session->activity_collection_url; 1328 1329 if (!activity_str) 1330 SVN_ERR(svn_ra_serf__v1_get_activity_collection( 1331 &activity_str, 1332 commit_ctx->session, 1333 scratch_pool, scratch_pool)); 1334 1335 commit_ctx->activity_url = svn_path_url_add_component2( 1336 activity_str, 1337 svn_uuid_generate(scratch_pool), 1338 commit_ctx->pool); 1339 1340 /* Create our activity URL now on the server. */ 1341 handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool); 1342 1343 handler->method = "MKACTIVITY"; 1344 handler->path = commit_ctx->activity_url; 1345 1346 handler->response_handler = svn_ra_serf__expect_empty_body; 1347 handler->response_baton = handler; 1348 1349 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 1350 1351 if (handler->sline.code != 201) 1352 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1353 1354 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */ 1355 SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url), 1356 commit_ctx->session, scratch_pool)); 1357 1358 1359 /* Build our directory baton. */ 1360 dir = apr_pcalloc(dir_pool, sizeof(*dir)); 1361 dir->pool = dir_pool; 1362 dir->commit_ctx = commit_ctx; 1363 dir->base_revision = base_revision; 1364 dir->relpath = ""; 1365 dir->name = ""; 1366 dir->prop_changes = apr_hash_make(dir->pool); 1367 1368 SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session, 1369 dir->relpath, 1370 dir->base_revision, commit_ctx->checked_in_url, 1371 dir->pool, scratch_pool)); 1372 commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url); 1373 1374 /* Checkout our root dir */ 1375 SVN_ERR(checkout_dir(dir, scratch_pool)); 1376 1377 proppatch_target = commit_ctx->baseline_url; 1378 } 1379 1380 /* Unless this is NULL -- which means we don't need to PROPPATCH the 1381 transaction with our revprops -- then, you know, PROPPATCH the 1382 transaction with our revprops. */ 1383 if (proppatch_target) 1384 { 1385 proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx)); 1386 proppatch_ctx->pool = scratch_pool; 1387 proppatch_ctx->commit_ctx = NULL; /* No lock info */ 1388 proppatch_ctx->path = proppatch_target; 1389 proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool); 1390 proppatch_ctx->base_revision = SVN_INVALID_REVNUM; 1391 1392 for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table); 1393 hi; 1394 hi = apr_hash_next(hi)) 1395 { 1396 svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop)); 1397 1398 prop->name = apr_hash_this_key(hi); 1399 prop->value = apr_hash_this_val(hi); 1400 1401 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop); 1402 } 1403 1404 SVN_ERR(proppatch_resource(commit_ctx->session, 1405 proppatch_ctx, scratch_pool)); 1406 } 1407 1408 svn_pool_destroy(scratch_pool); 1409 1410 *root_baton = dir; 1411 1412 return SVN_NO_ERROR; 1413} 1414 1415/* Implements svn_ra_serf__request_body_delegate_t */ 1416static svn_error_t * 1417create_delete_body(serf_bucket_t **body_bkt, 1418 void *baton, 1419 serf_bucket_alloc_t *alloc, 1420 apr_pool_t *pool /* request pool */, 1421 apr_pool_t *scratch_pool) 1422{ 1423 delete_context_t *ctx = baton; 1424 serf_bucket_t *body; 1425 1426 body = serf_bucket_aggregate_create(alloc); 1427 1428 svn_ra_serf__add_xml_header_buckets(body, alloc); 1429 1430 svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens, 1431 ctx->relpath, body, alloc, pool); 1432 1433 *body_bkt = body; 1434 return SVN_NO_ERROR; 1435} 1436 1437static svn_error_t * 1438delete_entry(const char *path, 1439 svn_revnum_t revision, 1440 void *parent_baton, 1441 apr_pool_t *pool) 1442{ 1443 dir_context_t *dir = parent_baton; 1444 delete_context_t *delete_ctx; 1445 svn_ra_serf__handler_t *handler; 1446 const char *delete_target; 1447 1448 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1449 { 1450 delete_target = svn_path_url_add_component2( 1451 dir->commit_ctx->txn_root_url, 1452 path, dir->pool); 1453 } 1454 else 1455 { 1456 /* Ensure our directory has been checked out */ 1457 SVN_ERR(checkout_dir(dir, pool /* scratch_pool */)); 1458 delete_target = svn_path_url_add_component2(dir->working_url, 1459 svn_relpath_basename(path, 1460 NULL), 1461 pool); 1462 } 1463 1464 /* DELETE our entry */ 1465 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx)); 1466 delete_ctx->relpath = apr_pstrdup(pool, path); 1467 delete_ctx->revision = revision; 1468 delete_ctx->commit_ctx = dir->commit_ctx; 1469 1470 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool); 1471 1472 handler->response_handler = svn_ra_serf__expect_empty_body; 1473 handler->response_baton = handler; 1474 1475 handler->header_delegate = setup_delete_headers; 1476 handler->header_delegate_baton = delete_ctx; 1477 1478 handler->method = "DELETE"; 1479 handler->path = delete_target; 1480 handler->no_fail_on_http_failure_status = TRUE; 1481 1482 SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 1483 1484 if (handler->sline.code == 400) 1485 { 1486 /* Try again with non-standard body to overcome Apache Httpd 1487 header limit */ 1488 delete_ctx->non_recursive_if = TRUE; 1489 1490 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool); 1491 1492 handler->response_handler = svn_ra_serf__expect_empty_body; 1493 handler->response_baton = handler; 1494 1495 handler->header_delegate = setup_delete_headers; 1496 handler->header_delegate_baton = delete_ctx; 1497 1498 handler->method = "DELETE"; 1499 handler->path = delete_target; 1500 1501 handler->body_type = "text/xml"; 1502 handler->body_delegate = create_delete_body; 1503 handler->body_delegate_baton = delete_ctx; 1504 1505 SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 1506 } 1507 1508 if (handler->server_error) 1509 return svn_ra_serf__server_error_create(handler, pool); 1510 1511 /* 204 No Content: item successfully deleted */ 1512 if (handler->sline.code != 204) 1513 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1514 1515 svn_hash_sets(dir->commit_ctx->deleted_entries, 1516 apr_pstrdup(dir->commit_ctx->pool, path), (void *)1); 1517 1518 return SVN_NO_ERROR; 1519} 1520 1521static svn_error_t * 1522add_directory(const char *path, 1523 void *parent_baton, 1524 const char *copyfrom_path, 1525 svn_revnum_t copyfrom_revision, 1526 apr_pool_t *dir_pool, 1527 void **child_baton) 1528{ 1529 dir_context_t *parent = parent_baton; 1530 dir_context_t *dir; 1531 svn_ra_serf__handler_t *handler; 1532 apr_status_t status; 1533 const char *mkcol_target; 1534 1535 dir = apr_pcalloc(dir_pool, sizeof(*dir)); 1536 1537 dir->pool = dir_pool; 1538 dir->parent_dir = parent; 1539 dir->commit_ctx = parent->commit_ctx; 1540 dir->added = TRUE; 1541 dir->base_revision = SVN_INVALID_REVNUM; 1542 dir->copy_revision = copyfrom_revision; 1543 dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path); 1544 dir->relpath = apr_pstrdup(dir->pool, path); 1545 dir->name = svn_relpath_basename(dir->relpath, NULL); 1546 dir->prop_changes = apr_hash_make(dir->pool); 1547 1548 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1549 { 1550 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url, 1551 path, dir->pool); 1552 mkcol_target = dir->url; 1553 } 1554 else 1555 { 1556 /* Ensure our parent is checked out. */ 1557 SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */)); 1558 1559 dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url, 1560 dir->name, dir->pool); 1561 mkcol_target = svn_path_url_add_component2( 1562 parent->working_url, 1563 dir->name, dir->pool); 1564 } 1565 1566 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool); 1567 1568 handler->response_handler = svn_ra_serf__expect_empty_body; 1569 handler->response_baton = handler; 1570 if (!dir->copy_path) 1571 { 1572 handler->method = "MKCOL"; 1573 handler->path = mkcol_target; 1574 1575 handler->header_delegate = setup_add_dir_common_headers; 1576 handler->header_delegate_baton = dir; 1577 } 1578 else 1579 { 1580 apr_uri_t uri; 1581 const char *req_url; 1582 1583 status = apr_uri_parse(dir->pool, dir->copy_path, &uri); 1584 if (status) 1585 { 1586 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1587 _("Unable to parse URL '%s'"), 1588 dir->copy_path); 1589 } 1590 1591 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, 1592 dir->commit_ctx->session, 1593 uri.path, dir->copy_revision, 1594 dir_pool, dir_pool)); 1595 1596 handler->method = "COPY"; 1597 handler->path = req_url; 1598 1599 handler->header_delegate = setup_copy_dir_headers; 1600 handler->header_delegate_baton = dir; 1601 } 1602 /* We have the same problem as with DELETE here: if there are too many 1603 locks, the request fails. But in this case there is no way to retry 1604 with a non-standard request. #### How to fix? */ 1605 SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool)); 1606 1607 if (handler->sline.code != 201) 1608 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1609 1610 *child_baton = dir; 1611 1612 return SVN_NO_ERROR; 1613} 1614 1615static svn_error_t * 1616open_directory(const char *path, 1617 void *parent_baton, 1618 svn_revnum_t base_revision, 1619 apr_pool_t *dir_pool, 1620 void **child_baton) 1621{ 1622 dir_context_t *parent = parent_baton; 1623 dir_context_t *dir; 1624 1625 dir = apr_pcalloc(dir_pool, sizeof(*dir)); 1626 1627 dir->pool = dir_pool; 1628 1629 dir->parent_dir = parent; 1630 dir->commit_ctx = parent->commit_ctx; 1631 1632 dir->added = FALSE; 1633 dir->base_revision = base_revision; 1634 dir->relpath = apr_pstrdup(dir->pool, path); 1635 dir->name = svn_relpath_basename(dir->relpath, NULL); 1636 dir->prop_changes = apr_hash_make(dir->pool); 1637 1638 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1639 { 1640 dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url, 1641 path, dir->pool); 1642 } 1643 else 1644 { 1645 SVN_ERR(get_version_url(&dir->url, 1646 dir->commit_ctx->session, 1647 dir->relpath, dir->base_revision, 1648 dir->commit_ctx->checked_in_url, 1649 dir->pool, dir->pool /* scratch_pool */)); 1650 } 1651 *child_baton = dir; 1652 1653 return SVN_NO_ERROR; 1654} 1655 1656static svn_error_t * 1657change_dir_prop(void *dir_baton, 1658 const char *name, 1659 const svn_string_t *value, 1660 apr_pool_t *scratch_pool) 1661{ 1662 dir_context_t *dir = dir_baton; 1663 svn_prop_t *prop; 1664 1665 if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1666 { 1667 /* Ensure we have a checked out dir. */ 1668 SVN_ERR(checkout_dir(dir, scratch_pool)); 1669 } 1670 1671 prop = apr_palloc(dir->pool, sizeof(*prop)); 1672 1673 prop->name = apr_pstrdup(dir->pool, name); 1674 prop->value = svn_string_dup(value, dir->pool); 1675 1676 svn_hash_sets(dir->prop_changes, prop->name, prop); 1677 1678 return SVN_NO_ERROR; 1679} 1680 1681static svn_error_t * 1682close_directory(void *dir_baton, 1683 apr_pool_t *pool) 1684{ 1685 dir_context_t *dir = dir_baton; 1686 1687 /* Huh? We're going to be called before the texts are sent. Ugh. 1688 * Therefore, just wave politely at our caller. 1689 */ 1690 1691 /* PROPPATCH our prop change and pass it along. */ 1692 if (apr_hash_count(dir->prop_changes)) 1693 { 1694 proppatch_context_t *proppatch_ctx; 1695 1696 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx)); 1697 proppatch_ctx->pool = pool; 1698 proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */; 1699 proppatch_ctx->relpath = dir->relpath; 1700 proppatch_ctx->prop_changes = dir->prop_changes; 1701 proppatch_ctx->base_revision = dir->base_revision; 1702 1703 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1704 { 1705 proppatch_ctx->path = dir->url; 1706 } 1707 else 1708 { 1709 proppatch_ctx->path = dir->working_url; 1710 } 1711 1712 SVN_ERR(proppatch_resource(dir->commit_ctx->session, 1713 proppatch_ctx, dir->pool)); 1714 } 1715 1716 return SVN_NO_ERROR; 1717} 1718 1719static svn_error_t * 1720add_file(const char *path, 1721 void *parent_baton, 1722 const char *copy_path, 1723 svn_revnum_t copy_revision, 1724 apr_pool_t *file_pool, 1725 void **file_baton) 1726{ 1727 dir_context_t *dir = parent_baton; 1728 file_context_t *new_file; 1729 const char *deleted_parent = path; 1730 apr_pool_t *scratch_pool = svn_pool_create(file_pool); 1731 1732 new_file = apr_pcalloc(file_pool, sizeof(*new_file)); 1733 new_file->pool = file_pool; 1734 1735 dir->ref_count++; 1736 1737 new_file->parent_dir = dir; 1738 new_file->commit_ctx = dir->commit_ctx; 1739 new_file->relpath = apr_pstrdup(new_file->pool, path); 1740 new_file->name = svn_relpath_basename(new_file->relpath, NULL); 1741 new_file->added = TRUE; 1742 new_file->base_revision = SVN_INVALID_REVNUM; 1743 new_file->copy_path = apr_pstrdup(new_file->pool, copy_path); 1744 new_file->copy_revision = copy_revision; 1745 new_file->prop_changes = apr_hash_make(new_file->pool); 1746 1747 /* Ensure that the file doesn't exist by doing a HEAD on the 1748 resource. If we're using HTTP v2, we'll just look into the 1749 transaction root tree for this thing. */ 1750 if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) 1751 { 1752 new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url, 1753 path, new_file->pool); 1754 } 1755 else 1756 { 1757 /* Ensure our parent directory has been checked out */ 1758 SVN_ERR(checkout_dir(dir, scratch_pool)); 1759 1760 new_file->url = 1761 svn_path_url_add_component2(dir->working_url, 1762 new_file->name, new_file->pool); 1763 } 1764 1765 while (deleted_parent && deleted_parent[0] != '\0') 1766 { 1767 if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent)) 1768 { 1769 break; 1770 } 1771 deleted_parent = svn_relpath_dirname(deleted_parent, file_pool); 1772 } 1773 1774 if (copy_path) 1775 { 1776 svn_ra_serf__handler_t *handler; 1777 apr_uri_t uri; 1778 const char *req_url; 1779 apr_status_t status; 1780 1781 /* Create the copy directly as cheap 'does exist/out of date' 1782 check. We update the copy (if needed) from close_file() */ 1783 1784 status = apr_uri_parse(scratch_pool, copy_path, &uri); 1785 if (status) 1786 return svn_ra_serf__wrap_err(status, NULL); 1787 1788 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, 1789 dir->commit_ctx->session, 1790 uri.path, copy_revision, 1791 scratch_pool, scratch_pool)); 1792 1793 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, 1794 scratch_pool); 1795 handler->method = "COPY"; 1796 handler->path = req_url; 1797 1798 handler->response_handler = svn_ra_serf__expect_empty_body; 1799 handler->response_baton = handler; 1800 1801 handler->header_delegate = setup_copy_file_headers; 1802 handler->header_delegate_baton = new_file; 1803 1804 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 1805 1806 if (handler->sline.code != 201) 1807 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1808 } 1809 else if (! ((dir->added && !dir->copy_path) || 1810 (deleted_parent && deleted_parent[0] != '\0'))) 1811 { 1812 svn_ra_serf__handler_t *handler; 1813 svn_error_t *err; 1814 1815 handler = svn_ra_serf__create_handler(dir->commit_ctx->session, 1816 scratch_pool); 1817 handler->method = "HEAD"; 1818 handler->path = svn_path_url_add_component2( 1819 dir->commit_ctx->session->session_url.path, 1820 path, scratch_pool); 1821 handler->response_handler = svn_ra_serf__expect_empty_body; 1822 handler->response_baton = handler; 1823 handler->no_dav_headers = TRUE; /* Read only operation outside txn */ 1824 1825 err = svn_ra_serf__context_run_one(handler, scratch_pool); 1826 1827 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1828 { 1829 svn_error_clear(err); /* Great. We can create a new file! */ 1830 } 1831 else if (err) 1832 return svn_error_trace(err); 1833 else 1834 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1835 _("File '%s' already exists"), path); 1836 } 1837 1838 svn_pool_destroy(scratch_pool); 1839 *file_baton = new_file; 1840 1841 return SVN_NO_ERROR; 1842} 1843 1844static svn_error_t * 1845open_file(const char *path, 1846 void *parent_baton, 1847 svn_revnum_t base_revision, 1848 apr_pool_t *file_pool, 1849 void **file_baton) 1850{ 1851 dir_context_t *parent = parent_baton; 1852 file_context_t *new_file; 1853 1854 new_file = apr_pcalloc(file_pool, sizeof(*new_file)); 1855 new_file->pool = file_pool; 1856 1857 parent->ref_count++; 1858 1859 new_file->parent_dir = parent; 1860 new_file->commit_ctx = parent->commit_ctx; 1861 new_file->relpath = apr_pstrdup(new_file->pool, path); 1862 new_file->name = svn_relpath_basename(new_file->relpath, NULL); 1863 new_file->added = FALSE; 1864 new_file->base_revision = base_revision; 1865 new_file->prop_changes = apr_hash_make(new_file->pool); 1866 1867 if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx)) 1868 { 1869 new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url, 1870 path, new_file->pool); 1871 } 1872 else 1873 { 1874 /* CHECKOUT the file into our activity. */ 1875 SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */)); 1876 1877 new_file->url = new_file->working_url; 1878 } 1879 1880 *file_baton = new_file; 1881 1882 return SVN_NO_ERROR; 1883} 1884 1885/* Implements svn_stream_lazyopen_func_t for apply_textdelta */ 1886static svn_error_t * 1887delayed_commit_stream_open(svn_stream_t **stream, 1888 void *baton, 1889 apr_pool_t *result_pool, 1890 apr_pool_t *scratch_pool) 1891{ 1892 file_context_t *file_ctx = baton; 1893 1894 SVN_ERR(svn_io_open_unique_file3(&file_ctx->svndiff, NULL, NULL, 1895 svn_io_file_del_on_pool_cleanup, 1896 file_ctx->pool, scratch_pool)); 1897 1898 *stream = svn_stream_from_aprfile2(file_ctx->svndiff, TRUE, result_pool); 1899 return SVN_NO_ERROR; 1900} 1901 1902static svn_error_t * 1903apply_textdelta(void *file_baton, 1904 const char *base_checksum, 1905 apr_pool_t *pool, 1906 svn_txdelta_window_handler_t *handler, 1907 void **handler_baton) 1908{ 1909 file_context_t *ctx = file_baton; 1910 1911 /* Store the stream in a temporary file; we'll give it to serf when we 1912 * close this file. 1913 * 1914 * TODO: There should be a way we can stream the request body instead of 1915 * writing to a temporary file (ugh). A special svn stream serf bucket 1916 * that returns EAGAIN until we receive the done call? But, when 1917 * would we run through the serf context? Grr. 1918 */ 1919 1920 ctx->stream = svn_stream_lazyopen_create(delayed_commit_stream_open, 1921 ctx, FALSE, ctx->pool); 1922 1923 svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0, 1924 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 1925 1926 if (base_checksum) 1927 ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum); 1928 1929 return SVN_NO_ERROR; 1930} 1931 1932static svn_error_t * 1933change_file_prop(void *file_baton, 1934 const char *name, 1935 const svn_string_t *value, 1936 apr_pool_t *pool) 1937{ 1938 file_context_t *file = file_baton; 1939 svn_prop_t *prop; 1940 1941 prop = apr_palloc(file->pool, sizeof(*prop)); 1942 1943 prop->name = apr_pstrdup(file->pool, name); 1944 prop->value = svn_string_dup(value, file->pool); 1945 1946 svn_hash_sets(file->prop_changes, prop->name, prop); 1947 1948 return SVN_NO_ERROR; 1949} 1950 1951static svn_error_t * 1952close_file(void *file_baton, 1953 const char *text_checksum, 1954 apr_pool_t *scratch_pool) 1955{ 1956 file_context_t *ctx = file_baton; 1957 svn_boolean_t put_empty_file = FALSE; 1958 1959 ctx->result_checksum = text_checksum; 1960 1961 /* If we got no stream of changes, but this is an added-without-history 1962 * file, make a note that we'll be PUTting a zero-byte file to the server. 1963 */ 1964 if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path)) 1965 put_empty_file = TRUE; 1966 1967 /* If we had a stream of changes, push them to the server... */ 1968 if (ctx->svndiff || put_empty_file) 1969 { 1970 svn_ra_serf__handler_t *handler; 1971 int expected_result; 1972 1973 handler = svn_ra_serf__create_handler(ctx->commit_ctx->session, 1974 scratch_pool); 1975 1976 handler->method = "PUT"; 1977 handler->path = ctx->url; 1978 1979 handler->response_handler = svn_ra_serf__expect_empty_body; 1980 handler->response_baton = handler; 1981 1982 if (put_empty_file) 1983 { 1984 handler->body_delegate = create_empty_put_body; 1985 handler->body_delegate_baton = ctx; 1986 handler->body_type = "text/plain"; 1987 } 1988 else 1989 { 1990 handler->body_delegate = create_put_body; 1991 handler->body_delegate_baton = ctx; 1992 handler->body_type = SVN_SVNDIFF_MIME_TYPE; 1993 } 1994 1995 handler->header_delegate = setup_put_headers; 1996 handler->header_delegate_baton = ctx; 1997 1998 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 1999 2000 if (ctx->added && ! ctx->copy_path) 2001 expected_result = 201; /* Created */ 2002 else 2003 expected_result = 204; /* Updated */ 2004 2005 if (handler->sline.code != expected_result) 2006 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 2007 } 2008 2009 if (ctx->svndiff) 2010 SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool)); 2011 2012 /* If we had any prop changes, push them via PROPPATCH. */ 2013 if (apr_hash_count(ctx->prop_changes)) 2014 { 2015 proppatch_context_t *proppatch; 2016 2017 proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch)); 2018 proppatch->pool = scratch_pool; 2019 proppatch->relpath = ctx->relpath; 2020 proppatch->path = ctx->url; 2021 proppatch->commit_ctx = ctx->commit_ctx; 2022 proppatch->prop_changes = ctx->prop_changes; 2023 proppatch->base_revision = ctx->base_revision; 2024 2025 SVN_ERR(proppatch_resource(ctx->commit_ctx->session, 2026 proppatch, scratch_pool)); 2027 } 2028 2029 return SVN_NO_ERROR; 2030} 2031 2032static svn_error_t * 2033close_edit(void *edit_baton, 2034 apr_pool_t *pool) 2035{ 2036 commit_context_t *ctx = edit_baton; 2037 const char *merge_target = 2038 ctx->activity_url ? ctx->activity_url : ctx->txn_url; 2039 const svn_commit_info_t *commit_info; 2040 svn_error_t *err = NULL; 2041 2042 /* MERGE our activity */ 2043 SVN_ERR(svn_ra_serf__run_merge(&commit_info, 2044 ctx->session, 2045 merge_target, 2046 ctx->lock_tokens, 2047 ctx->keep_locks, 2048 pool, pool)); 2049 2050 ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */ 2051 2052 /* Inform the WC that we did a commit. */ 2053 if (ctx->callback) 2054 err = ctx->callback(commit_info, ctx->callback_baton, pool); 2055 2056 /* If we're using activities, DELETE our completed activity. */ 2057 if (ctx->activity_url) 2058 { 2059 svn_ra_serf__handler_t *handler; 2060 2061 handler = svn_ra_serf__create_handler(ctx->session, pool); 2062 2063 handler->method = "DELETE"; 2064 handler->path = ctx->activity_url; 2065 2066 handler->response_handler = svn_ra_serf__expect_empty_body; 2067 handler->response_baton = handler; 2068 2069 ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */ 2070 2071 SVN_ERR(svn_error_compose_create( 2072 err, 2073 svn_ra_serf__context_run_one(handler, pool))); 2074 2075 if (handler->sline.code != 204) 2076 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 2077 } 2078 2079 SVN_ERR(err); 2080 2081 return SVN_NO_ERROR; 2082} 2083 2084static svn_error_t * 2085abort_edit(void *edit_baton, 2086 apr_pool_t *pool) 2087{ 2088 commit_context_t *ctx = edit_baton; 2089 svn_ra_serf__handler_t *handler; 2090 2091 /* If an activity or transaction wasn't even created, don't bother 2092 trying to delete it. */ 2093 if (! (ctx->activity_url || ctx->txn_url)) 2094 return SVN_NO_ERROR; 2095 2096 /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection 2097 had a problem. We need to reset it, in order to use it again. */ 2098 serf_connection_reset(ctx->session->conns[0]->conn); 2099 2100 /* DELETE our aborted activity */ 2101 handler = svn_ra_serf__create_handler(ctx->session, pool); 2102 2103 handler->method = "DELETE"; 2104 2105 handler->response_handler = svn_ra_serf__expect_empty_body; 2106 handler->response_baton = handler; 2107 handler->no_fail_on_http_failure_status = TRUE; 2108 2109 if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */ 2110 handler->path = ctx->txn_url; 2111 else 2112 handler->path = ctx->activity_url; 2113 2114 SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 2115 2116 /* 204 if deleted, 2117 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too), 2118 404 if the activity wasn't found. */ 2119 if (handler->sline.code != 204 2120 && handler->sline.code != 403 2121 && handler->sline.code != 404) 2122 { 2123 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 2124 } 2125 2126 /* Don't delete again if somebody aborts twice */ 2127 ctx->activity_url = NULL; 2128 ctx->txn_url = NULL; 2129 2130 return SVN_NO_ERROR; 2131} 2132 2133svn_error_t * 2134svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session, 2135 const svn_delta_editor_t **ret_editor, 2136 void **edit_baton, 2137 apr_hash_t *revprop_table, 2138 svn_commit_callback2_t callback, 2139 void *callback_baton, 2140 apr_hash_t *lock_tokens, 2141 svn_boolean_t keep_locks, 2142 apr_pool_t *pool) 2143{ 2144 svn_ra_serf__session_t *session = ra_session->priv; 2145 svn_delta_editor_t *editor; 2146 commit_context_t *ctx; 2147 const char *repos_root; 2148 const char *base_relpath; 2149 svn_boolean_t supports_ephemeral_props; 2150 2151 ctx = apr_pcalloc(pool, sizeof(*ctx)); 2152 2153 ctx->pool = pool; 2154 2155 ctx->session = session; 2156 2157 ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool); 2158 2159 /* If the server supports ephemeral properties, add some carrying 2160 interesting version information. */ 2161 SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props, 2162 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 2163 pool)); 2164 if (supports_ephemeral_props) 2165 { 2166 svn_hash_sets(ctx->revprop_table, 2167 apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION), 2168 svn_string_create(SVN_VER_NUMBER, pool)); 2169 svn_hash_sets(ctx->revprop_table, 2170 apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT), 2171 svn_string_create(session->useragent, pool)); 2172 } 2173 2174 ctx->callback = callback; 2175 ctx->callback_baton = callback_baton; 2176 2177 ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens)) 2178 ? lock_tokens : NULL; 2179 ctx->keep_locks = keep_locks; 2180 2181 ctx->deleted_entries = apr_hash_make(ctx->pool); 2182 2183 editor = svn_delta_default_editor(pool); 2184 editor->open_root = open_root; 2185 editor->delete_entry = delete_entry; 2186 editor->add_directory = add_directory; 2187 editor->open_directory = open_directory; 2188 editor->change_dir_prop = change_dir_prop; 2189 editor->close_directory = close_directory; 2190 editor->add_file = add_file; 2191 editor->open_file = open_file; 2192 editor->apply_textdelta = apply_textdelta; 2193 editor->change_file_prop = change_file_prop; 2194 editor->close_file = close_file; 2195 editor->close_edit = close_edit; 2196 editor->abort_edit = abort_edit; 2197 2198 *ret_editor = editor; 2199 *edit_baton = ctx; 2200 2201 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool)); 2202 base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str, 2203 pool); 2204 2205 SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor, 2206 *edit_baton, repos_root, base_relpath, 2207 session->shim_callbacks, pool, pool)); 2208 2209 return SVN_NO_ERROR; 2210} 2211 2212svn_error_t * 2213svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session, 2214 svn_revnum_t rev, 2215 const char *name, 2216 const svn_string_t *const *old_value_p, 2217 const svn_string_t *value, 2218 apr_pool_t *pool) 2219{ 2220 svn_ra_serf__session_t *session = ra_session->priv; 2221 proppatch_context_t *proppatch_ctx; 2222 const char *proppatch_target; 2223 const svn_string_t *tmp_old_value; 2224 svn_boolean_t atomic_capable = FALSE; 2225 svn_prop_t *prop; 2226 svn_error_t *err; 2227 2228 if (old_value_p || !value) 2229 SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable, 2230 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 2231 pool)); 2232 2233 if (old_value_p) 2234 { 2235 /* How did you get past the same check in svn_ra_change_rev_prop2()? */ 2236 SVN_ERR_ASSERT(atomic_capable); 2237 } 2238 else if (! value && atomic_capable) 2239 { 2240 svn_string_t *old_value; 2241 /* mod_dav_svn doesn't report a failure when a property delete fails. The 2242 atomic revprop change behavior is a nice workaround, to allow getting 2243 access to the error anyway. 2244 2245 Somehow the mod_dav maintainers think that returning an error from 2246 mod_dav's property delete is an RFC violation. 2247 See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */ 2248 2249 SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value, 2250 pool)); 2251 2252 if (!old_value) 2253 return SVN_NO_ERROR; /* Nothing to delete */ 2254 2255 /* The api expects a double const pointer. Let's make one */ 2256 tmp_old_value = old_value; 2257 old_value_p = &tmp_old_value; 2258 } 2259 2260 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 2261 { 2262 proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev); 2263 } 2264 else 2265 { 2266 const char *vcc_url; 2267 2268 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); 2269 2270 SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target, 2271 session, vcc_url, rev, "href", 2272 pool, pool)); 2273 } 2274 2275 /* PROPPATCH our log message and pass it along. */ 2276 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx)); 2277 proppatch_ctx->pool = pool; 2278 proppatch_ctx->commit_ctx = NULL; /* No lock headers */ 2279 proppatch_ctx->path = proppatch_target; 2280 proppatch_ctx->prop_changes = apr_hash_make(pool); 2281 proppatch_ctx->base_revision = SVN_INVALID_REVNUM; 2282 2283 if (old_value_p) 2284 { 2285 prop = apr_palloc(pool, sizeof (*prop)); 2286 2287 prop->name = name; 2288 prop->value = *old_value_p; 2289 2290 proppatch_ctx->old_props = apr_hash_make(pool); 2291 svn_hash_sets(proppatch_ctx->old_props, prop->name, prop); 2292 } 2293 2294 prop = apr_palloc(pool, sizeof (*prop)); 2295 2296 prop->name = name; 2297 prop->value = value; 2298 svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop); 2299 2300 err = proppatch_resource(session, proppatch_ctx, pool); 2301 2302 /* Use specific error code for old property value mismatch. 2303 Use loop to provide the right result with tracing */ 2304 if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED) 2305 { 2306 svn_error_t *e = err; 2307 2308 while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED) 2309 { 2310 e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH; 2311 e = e->child; 2312 } 2313 } 2314 2315 return svn_error_trace(err); 2316} 2317