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