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