1/* 2 * commit.c: wrappers around wc commit functionality. 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/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <string.h> 31#include <apr_strings.h> 32#include <apr_hash.h> 33#include "svn_hash.h" 34#include "svn_wc.h" 35#include "svn_ra.h" 36#include "svn_client.h" 37#include "svn_string.h" 38#include "svn_pools.h" 39#include "svn_error.h" 40#include "svn_error_codes.h" 41#include "svn_dirent_uri.h" 42#include "svn_path.h" 43#include "svn_sorts.h" 44 45#include "client.h" 46#include "private/svn_wc_private.h" 47#include "private/svn_ra_private.h" 48 49#include "svn_private_config.h" 50 51struct capture_baton_t { 52 svn_commit_callback2_t original_callback; 53 void *original_baton; 54 55 svn_commit_info_t **info; 56 apr_pool_t *pool; 57}; 58 59 60static svn_error_t * 61capture_commit_info(const svn_commit_info_t *commit_info, 62 void *baton, 63 apr_pool_t *pool) 64{ 65 struct capture_baton_t *cb = baton; 66 67 *(cb->info) = svn_commit_info_dup(commit_info, cb->pool); 68 69 if (cb->original_callback) 70 SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool)); 71 72 return SVN_NO_ERROR; 73} 74 75 76static svn_error_t * 77get_ra_editor(const svn_delta_editor_t **editor, 78 void **edit_baton, 79 svn_ra_session_t *ra_session, 80 svn_client_ctx_t *ctx, 81 const char *log_msg, 82 const apr_array_header_t *commit_items, 83 const apr_hash_t *revprop_table, 84 apr_hash_t *lock_tokens, 85 svn_boolean_t keep_locks, 86 svn_commit_callback2_t commit_callback, 87 void *commit_baton, 88 apr_pool_t *pool) 89{ 90 apr_hash_t *commit_revprops; 91 apr_hash_t *relpath_map = NULL; 92 93 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 94 log_msg, ctx, pool)); 95 96#ifdef ENABLE_EV2_SHIMS 97 if (commit_items) 98 { 99 int i; 100 apr_pool_t *iterpool = svn_pool_create(pool); 101 102 relpath_map = apr_hash_make(pool); 103 for (i = 0; i < commit_items->nelts; i++) 104 { 105 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i, 106 svn_client_commit_item3_t *); 107 const char *relpath; 108 109 if (!item->path) 110 continue; 111 112 svn_pool_clear(iterpool); 113 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL, 114 ctx->wc_ctx, item->path, FALSE, pool, 115 iterpool)); 116 if (relpath) 117 svn_hash_sets(relpath_map, relpath, item->path); 118 } 119 svn_pool_destroy(iterpool); 120 } 121#endif 122 123 /* Fetch RA commit editor. */ 124 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 125 svn_client__get_shim_callbacks(ctx->wc_ctx, 126 relpath_map, pool))); 127 SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton, 128 commit_revprops, commit_callback, 129 commit_baton, lock_tokens, keep_locks, 130 pool)); 131 132 return SVN_NO_ERROR; 133} 134 135 136/*** Public Interfaces. ***/ 137 138static svn_error_t * 139reconcile_errors(svn_error_t *commit_err, 140 svn_error_t *unlock_err, 141 svn_error_t *bump_err, 142 apr_pool_t *pool) 143{ 144 svn_error_t *err; 145 146 /* Early release (for good behavior). */ 147 if (! (commit_err || unlock_err || bump_err)) 148 return SVN_NO_ERROR; 149 150 /* If there was a commit error, start off our error chain with 151 that. */ 152 if (commit_err) 153 { 154 commit_err = svn_error_quick_wrap 155 (commit_err, _("Commit failed (details follow):")); 156 err = commit_err; 157 } 158 159 /* Else, create a new "general" error that will lead off the errors 160 that follow. */ 161 else 162 err = svn_error_create(SVN_ERR_BASE, NULL, 163 _("Commit succeeded, but other errors follow:")); 164 165 /* If there was an unlock error... */ 166 if (unlock_err) 167 { 168 /* Wrap the error with some headers. */ 169 unlock_err = svn_error_quick_wrap 170 (unlock_err, _("Error unlocking locked dirs (details follow):")); 171 172 /* Append this error to the chain. */ 173 svn_error_compose(err, unlock_err); 174 } 175 176 /* If there was a bumping error... */ 177 if (bump_err) 178 { 179 /* Wrap the error with some headers. */ 180 bump_err = svn_error_quick_wrap 181 (bump_err, _("Error bumping revisions post-commit (details follow):")); 182 183 /* Append this error to the chain. */ 184 svn_error_compose(err, bump_err); 185 } 186 187 return err; 188} 189 190/* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them 191 to a new hashtable allocated in POOL. *RESULT is set to point to this 192 new hash table. *RESULT will be keyed on const char * URI-decoded paths 193 relative to BASE_URL. The lock tokens will not be duplicated. */ 194static svn_error_t * 195collect_lock_tokens(apr_hash_t **result, 196 apr_hash_t *all_tokens, 197 const char *base_url, 198 apr_pool_t *pool) 199{ 200 apr_hash_index_t *hi; 201 202 *result = apr_hash_make(pool); 203 204 for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi)) 205 { 206 const char *url = svn__apr_hash_index_key(hi); 207 const char *token = svn__apr_hash_index_val(hi); 208 const char *relpath = svn_uri_skip_ancestor(base_url, url, pool); 209 210 if (relpath) 211 { 212 svn_hash_sets(*result, relpath, token); 213 } 214 } 215 216 return SVN_NO_ERROR; 217} 218 219/* Put ITEM onto QUEUE, allocating it in QUEUE's pool... 220 * If a checksum is provided, it can be the MD5 and/or the SHA1. */ 221static svn_error_t * 222post_process_commit_item(svn_wc_committed_queue_t *queue, 223 const svn_client_commit_item3_t *item, 224 svn_wc_context_t *wc_ctx, 225 svn_boolean_t keep_changelists, 226 svn_boolean_t keep_locks, 227 svn_boolean_t commit_as_operations, 228 const svn_checksum_t *sha1_checksum, 229 apr_pool_t *scratch_pool) 230{ 231 svn_boolean_t loop_recurse = FALSE; 232 svn_boolean_t remove_lock; 233 234 if (! commit_as_operations 235 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 236 && (item->kind == svn_node_dir) 237 && (item->copyfrom_url)) 238 loop_recurse = TRUE; 239 240 remove_lock = (! keep_locks && (item->state_flags 241 & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)); 242 243 /* When the node was deleted (or replaced), we need to always remove the 244 locks, as they're invalidated on the server. We cannot honor the 245 SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell 246 us whether we have locked children. */ 247 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 248 remove_lock = TRUE; 249 250 return svn_wc_queue_committed3(queue, wc_ctx, item->path, 251 loop_recurse, item->incoming_prop_changes, 252 remove_lock, !keep_changelists, 253 sha1_checksum, scratch_pool); 254} 255 256 257static svn_error_t * 258check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx, 259 const char *target_abspath, 260 svn_depth_t depth, 261 apr_pool_t *scratch_pool) 262{ 263 svn_node_kind_t kind; 264 265 SVN_ERR_ASSERT(depth != svn_depth_infinity); 266 267 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath, 268 TRUE, FALSE, scratch_pool)); 269 270 271 /* ### TODO(sd): This check is slightly too strict. It should be 272 ### possible to: 273 ### 274 ### * delete a directory containing only files when 275 ### depth==svn_depth_files; 276 ### 277 ### * delete a directory containing only files and empty 278 ### subdirs when depth==svn_depth_immediates. 279 ### 280 ### But for now, we insist on svn_depth_infinity if you're 281 ### going to delete a directory, because we're lazy and 282 ### trying to get depthy commits working in the first place. 283 ### 284 ### This would be fairly easy to fix, though: just, well, 285 ### check the above conditions! 286 ### 287 ### GJS: I think there may be some confusion here. there is 288 ### the depth of the commit, and the depth of a checked-out 289 ### directory in the working copy. Delete, by its nature, will 290 ### always delete all of its children, so it seems a bit 291 ### strange to worry about what is in the working copy. 292 */ 293 if (kind == svn_node_dir) 294 { 295 svn_wc_schedule_t schedule; 296 297 /* ### Looking at schedule is probably enough, no need for 298 pristine compare etc. */ 299 SVN_ERR(svn_wc__node_get_schedule(&schedule, NULL, 300 wc_ctx, target_abspath, 301 scratch_pool)); 302 303 if (schedule == svn_wc_schedule_delete 304 || schedule == svn_wc_schedule_replace) 305 { 306 const apr_array_header_t *children; 307 308 SVN_ERR(svn_wc__node_get_children(&children, wc_ctx, 309 target_abspath, TRUE, 310 scratch_pool, scratch_pool)); 311 312 if (children->nelts > 0) 313 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 314 _("Cannot delete the directory '%s' " 315 "in a non-recursive commit " 316 "because it has children"), 317 svn_dirent_local_style(target_abspath, 318 scratch_pool)); 319 } 320 } 321 322 return SVN_NO_ERROR; 323} 324 325 326/* Given a list of committables described by their common base abspath 327 BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine 328 which absolute paths must be locked to commit all these targets and 329 return this as a const char * array in LOCK_TARGETS 330 331 Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary 332 storage */ 333static svn_error_t * 334determine_lock_targets(apr_array_header_t **lock_targets, 335 svn_wc_context_t *wc_ctx, 336 const char *base_abspath, 337 const apr_array_header_t *target_relpaths, 338 apr_pool_t *result_pool, 339 apr_pool_t *scratch_pool) 340{ 341 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 342 apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */ 343 apr_hash_index_t *hi; 344 int i; 345 346 wc_items = apr_hash_make(scratch_pool); 347 348 /* Create an array of targets for each working copy used */ 349 for (i = 0; i < target_relpaths->nelts; i++) 350 { 351 const char *target_abspath; 352 const char *wcroot_abspath; 353 apr_array_header_t *wc_targets; 354 svn_error_t *err; 355 const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i, 356 const char *); 357 358 svn_pool_clear(iterpool); 359 target_abspath = svn_dirent_join(base_abspath, target_relpath, 360 scratch_pool); 361 362 err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath, 363 iterpool, iterpool); 364 365 if (err) 366 { 367 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 368 { 369 svn_error_clear(err); 370 continue; 371 } 372 return svn_error_trace(err); 373 } 374 375 wc_targets = svn_hash_gets(wc_items, wcroot_abspath); 376 377 if (! wc_targets) 378 { 379 wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *)); 380 svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath), 381 wc_targets); 382 } 383 384 APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath; 385 } 386 387 *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items), 388 sizeof(const char *)); 389 390 /* For each working copy determine where to lock */ 391 for (hi = apr_hash_first(scratch_pool, wc_items); 392 hi; 393 hi = apr_hash_next(hi)) 394 { 395 const char *common; 396 const char *wcroot_abspath = svn__apr_hash_index_key(hi); 397 apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi); 398 399 svn_pool_clear(iterpool); 400 401 if (wc_targets->nelts == 1) 402 { 403 const char *target_abspath; 404 target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *); 405 406 if (! strcmp(wcroot_abspath, target_abspath)) 407 { 408 APR_ARRAY_PUSH(*lock_targets, const char *) 409 = apr_pstrdup(result_pool, target_abspath); 410 } 411 else 412 { 413 /* Lock the parent to allow deleting the target */ 414 APR_ARRAY_PUSH(*lock_targets, const char *) 415 = svn_dirent_dirname(target_abspath, result_pool); 416 } 417 } 418 else if (wc_targets->nelts > 1) 419 { 420 SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets, 421 FALSE, iterpool, iterpool)); 422 423 qsort(wc_targets->elts, wc_targets->nelts, wc_targets->elt_size, 424 svn_sort_compare_paths); 425 426 if (wc_targets->nelts == 0 427 || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*)) 428 || !strcmp(common, wcroot_abspath)) 429 { 430 APR_ARRAY_PUSH(*lock_targets, const char *) 431 = apr_pstrdup(result_pool, common); 432 } 433 else 434 { 435 /* Lock the parent to allow deleting the target */ 436 APR_ARRAY_PUSH(*lock_targets, const char *) 437 = svn_dirent_dirname(common, result_pool); 438 } 439 } 440 } 441 442 svn_pool_destroy(iterpool); 443 return SVN_NO_ERROR; 444} 445 446/* Baton for check_url_kind */ 447struct check_url_kind_baton 448{ 449 apr_pool_t *pool; 450 svn_ra_session_t *session; 451 const char *repos_root_url; 452 svn_client_ctx_t *ctx; 453}; 454 455/* Implements svn_client__check_url_kind_t for svn_client_commit5 */ 456static svn_error_t * 457check_url_kind(void *baton, 458 svn_node_kind_t *kind, 459 const char *url, 460 svn_revnum_t revision, 461 apr_pool_t *scratch_pool) 462{ 463 struct check_url_kind_baton *cukb = baton; 464 465 /* If we don't have a session or can't use the session, get one */ 466 if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url)) 467 { 468 SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx, 469 cukb->pool, scratch_pool)); 470 SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url, 471 cukb->pool)); 472 } 473 else 474 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool)); 475 476 return svn_error_trace( 477 svn_ra_check_path(cukb->session, "", revision, 478 kind, scratch_pool)); 479} 480 481/* Recurse into every target in REL_TARGETS, finding committable externals 482 * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS 483 * are assumed to be / will be created relative to BASE_ABSPATH. The remaining 484 * arguments correspond to those of svn_client_commit6(). */ 485static svn_error_t* 486append_externals_as_explicit_targets(apr_array_header_t *rel_targets, 487 const char *base_abspath, 488 svn_boolean_t include_file_externals, 489 svn_boolean_t include_dir_externals, 490 svn_depth_t depth, 491 svn_client_ctx_t *ctx, 492 apr_pool_t *result_pool, 493 apr_pool_t *scratch_pool) 494{ 495 int rel_targets_nelts_fixed; 496 int i; 497 apr_pool_t *iterpool; 498 499 if (! (include_file_externals || include_dir_externals)) 500 return SVN_NO_ERROR; 501 502 /* Easy part of applying DEPTH to externals. */ 503 if (depth == svn_depth_empty) 504 { 505 /* Don't recurse. */ 506 return SVN_NO_ERROR; 507 } 508 509 /* Iterate *and* grow REL_TARGETS at the same time. */ 510 rel_targets_nelts_fixed = rel_targets->nelts; 511 512 iterpool = svn_pool_create(scratch_pool); 513 514 for (i = 0; i < rel_targets_nelts_fixed; i++) 515 { 516 int j; 517 const char *target; 518 apr_array_header_t *externals = NULL; 519 520 svn_pool_clear(iterpool); 521 522 target = svn_dirent_join(base_abspath, 523 APR_ARRAY_IDX(rel_targets, i, const char *), 524 iterpool); 525 526 /* ### TODO: Possible optimization: No need to do this for file targets. 527 * ### But what's cheaper, stat'ing the file system or querying the db? 528 * ### --> future. */ 529 530 SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx, 531 target, depth, 532 iterpool, iterpool)); 533 534 if (externals != NULL) 535 { 536 const char *rel_target; 537 538 for (j = 0; j < externals->nelts; j++) 539 { 540 svn_wc__committable_external_info_t *xinfo = 541 APR_ARRAY_IDX(externals, j, 542 svn_wc__committable_external_info_t *); 543 544 if ((xinfo->kind == svn_node_file && ! include_file_externals) 545 || (xinfo->kind == svn_node_dir && ! include_dir_externals)) 546 continue; 547 548 rel_target = svn_dirent_skip_ancestor(base_abspath, 549 xinfo->local_abspath); 550 551 SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0'); 552 553 APR_ARRAY_PUSH(rel_targets, const char *) = 554 apr_pstrdup(result_pool, rel_target); 555 } 556 } 557 } 558 559 svn_pool_destroy(iterpool); 560 return SVN_NO_ERROR; 561} 562 563svn_error_t * 564svn_client_commit6(const apr_array_header_t *targets, 565 svn_depth_t depth, 566 svn_boolean_t keep_locks, 567 svn_boolean_t keep_changelists, 568 svn_boolean_t commit_as_operations, 569 svn_boolean_t include_file_externals, 570 svn_boolean_t include_dir_externals, 571 const apr_array_header_t *changelists, 572 const apr_hash_t *revprop_table, 573 svn_commit_callback2_t commit_callback, 574 void *commit_baton, 575 svn_client_ctx_t *ctx, 576 apr_pool_t *pool) 577{ 578 const svn_delta_editor_t *editor; 579 void *edit_baton; 580 struct capture_baton_t cb; 581 svn_ra_session_t *ra_session; 582 const char *log_msg; 583 const char *base_abspath; 584 const char *base_url; 585 apr_array_header_t *rel_targets; 586 apr_array_header_t *lock_targets; 587 apr_array_header_t *locks_obtained; 588 svn_client__committables_t *committables; 589 apr_hash_t *lock_tokens; 590 apr_hash_t *sha1_checksums; 591 apr_array_header_t *commit_items; 592 svn_error_t *cmt_err = SVN_NO_ERROR; 593 svn_error_t *bump_err = SVN_NO_ERROR; 594 svn_error_t *unlock_err = SVN_NO_ERROR; 595 svn_boolean_t commit_in_progress = FALSE; 596 svn_boolean_t timestamp_sleep = FALSE; 597 svn_commit_info_t *commit_info = NULL; 598 apr_pool_t *iterpool = svn_pool_create(pool); 599 const char *current_abspath; 600 const char *notify_prefix; 601 int depth_empty_after = -1; 602 int i; 603 604 SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude); 605 606 /* Committing URLs doesn't make sense, so error if it's tried. */ 607 for (i = 0; i < targets->nelts; i++) 608 { 609 const char *target = APR_ARRAY_IDX(targets, i, const char *); 610 if (svn_path_is_url(target)) 611 return svn_error_createf 612 (SVN_ERR_ILLEGAL_TARGET, NULL, 613 _("'%s' is a URL, but URLs cannot be commit targets"), target); 614 } 615 616 /* Condense the target list. This makes all targets absolute. */ 617 SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets, 618 FALSE, pool, iterpool)); 619 620 /* No targets means nothing to commit, so just return. */ 621 if (base_abspath == NULL) 622 return SVN_NO_ERROR; 623 624 SVN_ERR_ASSERT(rel_targets != NULL); 625 626 /* If we calculated only a base and no relative targets, this 627 must mean that we are being asked to commit (effectively) a 628 single path. */ 629 if (rel_targets->nelts == 0) 630 APR_ARRAY_PUSH(rel_targets, const char *) = ""; 631 632 if (include_file_externals || include_dir_externals) 633 { 634 if (depth != svn_depth_unknown && depth != svn_depth_infinity) 635 { 636 /* All targets after this will be handled as depth empty */ 637 depth_empty_after = rel_targets->nelts; 638 } 639 640 SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath, 641 include_file_externals, 642 include_dir_externals, 643 depth, ctx, 644 pool, pool)); 645 } 646 647 SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath, 648 rel_targets, pool, iterpool)); 649 650 locks_obtained = apr_array_make(pool, lock_targets->nelts, 651 sizeof(const char *)); 652 653 for (i = 0; i < lock_targets->nelts; i++) 654 { 655 const char *lock_root; 656 const char *target = APR_ARRAY_IDX(lock_targets, i, const char *); 657 658 svn_pool_clear(iterpool); 659 660 cmt_err = svn_error_trace( 661 svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target, 662 FALSE, pool, iterpool)); 663 664 if (cmt_err) 665 goto cleanup; 666 667 APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root; 668 } 669 670 /* Determine prefix to strip from the commit notify messages */ 671 SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool)); 672 notify_prefix = svn_dirent_get_longest_ancestor(current_abspath, 673 base_abspath, 674 pool); 675 676 /* If a non-recursive commit is desired, do not allow a deleted directory 677 as one of the targets. */ 678 if (depth != svn_depth_infinity && ! commit_as_operations) 679 for (i = 0; i < rel_targets->nelts; i++) 680 { 681 const char *relpath = APR_ARRAY_IDX(rel_targets, i, const char *); 682 const char *target_abspath; 683 684 svn_pool_clear(iterpool); 685 686 target_abspath = svn_dirent_join(base_abspath, relpath, iterpool); 687 688 cmt_err = svn_error_trace( 689 check_nonrecursive_dir_delete(ctx->wc_ctx, target_abspath, 690 depth, iterpool)); 691 692 if (cmt_err) 693 goto cleanup; 694 } 695 696 /* Crawl the working copy for commit items. */ 697 { 698 struct check_url_kind_baton cukb; 699 700 /* Prepare for when we have a copy containing not-present nodes. */ 701 cukb.pool = iterpool; 702 cukb.session = NULL; /* ### Can we somehow reuse session? */ 703 cukb.repos_root_url = NULL; 704 cukb.ctx = ctx; 705 706 cmt_err = svn_error_trace( 707 svn_client__harvest_committables(&committables, 708 &lock_tokens, 709 base_abspath, 710 rel_targets, 711 depth_empty_after, 712 depth, 713 ! keep_locks, 714 changelists, 715 check_url_kind, 716 &cukb, 717 ctx, 718 pool, 719 iterpool)); 720 721 svn_pool_clear(iterpool); 722 } 723 724 if (cmt_err) 725 goto cleanup; 726 727 if (apr_hash_count(committables->by_repository) == 0) 728 { 729 goto cleanup; /* Nothing to do */ 730 } 731 else if (apr_hash_count(committables->by_repository) > 1) 732 { 733 cmt_err = svn_error_create( 734 SVN_ERR_UNSUPPORTED_FEATURE, NULL, 735 _("Commit can only commit to a single repository at a time.\n" 736 "Are all targets part of the same working copy?")); 737 goto cleanup; 738 } 739 740 { 741 apr_hash_index_t *hi = apr_hash_first(iterpool, 742 committables->by_repository); 743 744 commit_items = svn__apr_hash_index_val(hi); 745 } 746 747 /* If our array of targets contains only locks (and no actual file 748 or prop modifications), then we return here to avoid committing a 749 revision with no changes. */ 750 { 751 svn_boolean_t found_changed_path = FALSE; 752 753 for (i = 0; i < commit_items->nelts; ++i) 754 { 755 svn_client_commit_item3_t *item = 756 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 757 758 if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN) 759 { 760 found_changed_path = TRUE; 761 break; 762 } 763 } 764 765 if (!found_changed_path) 766 goto cleanup; 767 } 768 769 /* For every target that was moved verify that both halves of the 770 * move are part of the commit. */ 771 for (i = 0; i < commit_items->nelts; i++) 772 { 773 svn_client_commit_item3_t *item = 774 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 775 776 svn_pool_clear(iterpool); 777 778 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE) 779 { 780 /* ### item->moved_from_abspath contains the move origin */ 781 const char *moved_from_abspath; 782 const char *delete_op_root_abspath; 783 784 cmt_err = svn_error_trace(svn_wc__node_was_moved_here( 785 &moved_from_abspath, 786 &delete_op_root_abspath, 787 ctx->wc_ctx, item->path, 788 iterpool, iterpool)); 789 if (cmt_err) 790 goto cleanup; 791 792 if (moved_from_abspath && delete_op_root_abspath && 793 strcmp(moved_from_abspath, delete_op_root_abspath) == 0) 794 795 { 796 svn_boolean_t found_delete_half = 797 (svn_hash_gets(committables->by_path, delete_op_root_abspath) 798 != NULL); 799 800 if (!found_delete_half) 801 { 802 const char *delete_half_parent_abspath; 803 804 /* The delete-half isn't in the commit target list. 805 * However, it might itself be the child of a deleted node, 806 * either because of another move or a deletion. 807 * 808 * For example, consider: mv A/B B; mv B/C C; commit; 809 * C's moved-from A/B/C is a child of the deleted A/B. 810 * A/B/C does not appear in the commit target list, but 811 * A/B does appear. 812 * (Note that moved-from information is always stored 813 * relative to the BASE tree, so we have 'C moved-from 814 * A/B/C', not 'C moved-from B/C'.) 815 * 816 * An example involving a move and a delete would be: 817 * mv A/B C; rm A; commit; 818 * Now C is moved-from A/B which does not appear in the 819 * commit target list, but A does appear. 820 */ 821 822 /* Scan upwards for a deletion op-root from the 823 * delete-half's parent directory. */ 824 delete_half_parent_abspath = 825 svn_dirent_dirname(delete_op_root_abspath, iterpool); 826 if (strcmp(delete_op_root_abspath, 827 delete_half_parent_abspath) != 0) 828 { 829 const char *parent_delete_op_root_abspath; 830 831 cmt_err = svn_error_trace( 832 svn_wc__node_get_deleted_ancestor( 833 &parent_delete_op_root_abspath, 834 ctx->wc_ctx, delete_half_parent_abspath, 835 iterpool, iterpool)); 836 if (cmt_err) 837 goto cleanup; 838 839 if (parent_delete_op_root_abspath) 840 found_delete_half = 841 (svn_hash_gets(committables->by_path, 842 parent_delete_op_root_abspath) 843 != NULL); 844 } 845 } 846 847 if (!found_delete_half) 848 { 849 cmt_err = svn_error_createf( 850 SVN_ERR_ILLEGAL_TARGET, NULL, 851 _("Cannot commit '%s' because it was moved from " 852 "'%s' which is not part of the commit; both " 853 "sides of the move must be committed together"), 854 svn_dirent_local_style(item->path, iterpool), 855 svn_dirent_local_style(delete_op_root_abspath, 856 iterpool)); 857 goto cleanup; 858 } 859 } 860 } 861 862 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 863 { 864 const char *moved_to_abspath; 865 const char *copy_op_root_abspath; 866 867 cmt_err = svn_error_trace(svn_wc__node_was_moved_away( 868 &moved_to_abspath, 869 ©_op_root_abspath, 870 ctx->wc_ctx, item->path, 871 iterpool, iterpool)); 872 if (cmt_err) 873 goto cleanup; 874 875 if (moved_to_abspath && copy_op_root_abspath && 876 strcmp(moved_to_abspath, copy_op_root_abspath) == 0 && 877 svn_hash_gets(committables->by_path, copy_op_root_abspath) 878 == NULL) 879 { 880 cmt_err = svn_error_createf( 881 SVN_ERR_ILLEGAL_TARGET, NULL, 882 _("Cannot commit '%s' because it was moved to '%s' " 883 "which is not part of the commit; both sides of " 884 "the move must be committed together"), 885 svn_dirent_local_style(item->path, iterpool), 886 svn_dirent_local_style(copy_op_root_abspath, 887 iterpool)); 888 goto cleanup; 889 } 890 } 891 } 892 893 /* Go get a log message. If an error occurs, or no log message is 894 specified, abort the operation. */ 895 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 896 { 897 const char *tmp_file; 898 cmt_err = svn_error_trace( 899 svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, 900 ctx, pool)); 901 902 if (cmt_err || (! log_msg)) 903 goto cleanup; 904 } 905 else 906 log_msg = ""; 907 908 /* Sort and condense our COMMIT_ITEMS. */ 909 cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url, 910 commit_items, 911 pool)); 912 913 if (cmt_err) 914 goto cleanup; 915 916 /* Collect our lock tokens with paths relative to base_url. */ 917 cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens, 918 base_url, pool)); 919 920 if (cmt_err) 921 goto cleanup; 922 923 cb.original_callback = commit_callback; 924 cb.original_baton = commit_baton; 925 cb.info = &commit_info; 926 cb.pool = pool; 927 928 /* Get the RA editor from the first lock target, rather than BASE_ABSPATH. 929 * When committing from multiple WCs, BASE_ABSPATH might be an unrelated 930 * parent of nested working copies. We don't support commits to multiple 931 * repositories so using the first WC to get the RA session is safe. */ 932 cmt_err = svn_error_trace( 933 svn_client__open_ra_session_internal(&ra_session, NULL, base_url, 934 APR_ARRAY_IDX(lock_targets, 935 0, 936 const char *), 937 commit_items, 938 TRUE, TRUE, ctx, 939 pool, pool)); 940 941 if (cmt_err) 942 goto cleanup; 943 944 cmt_err = svn_error_trace( 945 get_ra_editor(&editor, &edit_baton, ra_session, ctx, 946 log_msg, commit_items, revprop_table, 947 lock_tokens, keep_locks, capture_commit_info, 948 &cb, pool)); 949 950 if (cmt_err) 951 goto cleanup; 952 953 /* Make a note that we have a commit-in-progress. */ 954 commit_in_progress = TRUE; 955 956 /* We'll assume that, once we pass this point, we are going to need to 957 * sleep for timestamps. Really, we may not need to do unless and until 958 * we reach the point where we post-commit 'bump' the WC metadata. */ 959 timestamp_sleep = TRUE; 960 961 /* Perform the commit. */ 962 cmt_err = svn_error_trace( 963 svn_client__do_commit(base_url, commit_items, editor, edit_baton, 964 notify_prefix, &sha1_checksums, ctx, pool, 965 iterpool)); 966 967 /* Handle a successful commit. */ 968 if ((! cmt_err) 969 || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED)) 970 { 971 svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool); 972 973 /* Make a note that our commit is finished. */ 974 commit_in_progress = FALSE; 975 976 for (i = 0; i < commit_items->nelts; i++) 977 { 978 svn_client_commit_item3_t *item 979 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 980 981 svn_pool_clear(iterpool); 982 bump_err = post_process_commit_item( 983 queue, item, ctx->wc_ctx, 984 keep_changelists, keep_locks, commit_as_operations, 985 svn_hash_gets(sha1_checksums, item->path), 986 iterpool); 987 if (bump_err) 988 goto cleanup; 989 } 990 991 SVN_ERR_ASSERT(commit_info); 992 bump_err = svn_wc_process_committed_queue2( 993 queue, ctx->wc_ctx, 994 commit_info->revision, 995 commit_info->date, 996 commit_info->author, 997 ctx->cancel_func, ctx->cancel_baton, 998 iterpool); 999 } 1000 1001 cleanup: 1002 /* Sleep to ensure timestamp integrity. */ 1003 if (timestamp_sleep) 1004 svn_io_sleep_for_timestamps(base_abspath, pool); 1005 1006 /* Abort the commit if it is still in progress. */ 1007 svn_pool_clear(iterpool); /* Close open handles before aborting */ 1008 if (commit_in_progress) 1009 cmt_err = svn_error_compose_create(cmt_err, 1010 editor->abort_edit(edit_baton, pool)); 1011 1012 /* A bump error is likely to occur while running a working copy log file, 1013 explicitly unlocking and removing temporary files would be wrong in 1014 that case. A commit error (cmt_err) should only occur before any 1015 attempt to modify the working copy, so it doesn't prevent explicit 1016 clean-up. */ 1017 if (! bump_err) 1018 { 1019 /* Release all locks we obtained */ 1020 for (i = 0; i < locks_obtained->nelts; i++) 1021 { 1022 const char *lock_root = APR_ARRAY_IDX(locks_obtained, i, 1023 const char *); 1024 1025 svn_pool_clear(iterpool); 1026 1027 unlock_err = svn_error_compose_create( 1028 svn_wc__release_write_lock(ctx->wc_ctx, lock_root, 1029 iterpool), 1030 unlock_err); 1031 } 1032 } 1033 1034 svn_pool_destroy(iterpool); 1035 1036 return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err, 1037 pool)); 1038} 1039