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