1/* 2 * commit_util.c: Driver for the WC commit process. 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#include <string.h> 28 29#include <apr_pools.h> 30#include <apr_hash.h> 31#include <apr_md5.h> 32 33#include "client.h" 34#include "svn_dirent_uri.h" 35#include "svn_path.h" 36#include "svn_types.h" 37#include "svn_pools.h" 38#include "svn_props.h" 39#include "svn_iter.h" 40#include "svn_hash.h" 41 42#include <assert.h> 43 44#include "svn_private_config.h" 45#include "private/svn_wc_private.h" 46#include "private/svn_client_private.h" 47#include "private/svn_sorts_private.h" 48 49/*** Uncomment this to turn on commit driver debugging. ***/ 50/* 51#define SVN_CLIENT_COMMIT_DEBUG 52*/ 53 54/* Wrap an RA error in a nicer error if one is available. */ 55static svn_error_t * 56fixup_commit_error(const char *local_abspath, 57 const char *base_url, 58 const char *path, 59 svn_node_kind_t kind, 60 svn_error_t *err, 61 svn_client_ctx_t *ctx, 62 apr_pool_t *scratch_pool) 63{ 64 if (err->apr_err == SVN_ERR_FS_NOT_FOUND 65 || err->apr_err == SVN_ERR_FS_CONFLICT 66 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS 67 || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE 68 || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND 69 || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS 70 || err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED 71 || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE)) 72 { 73 if (ctx->notify_func2) 74 { 75 svn_wc_notify_t *notify; 76 77 if (local_abspath) 78 notify = svn_wc_create_notify(local_abspath, 79 svn_wc_notify_failed_out_of_date, 80 scratch_pool); 81 else 82 notify = svn_wc_create_notify_url( 83 svn_path_url_add_component2(base_url, path, 84 scratch_pool), 85 svn_wc_notify_failed_out_of_date, 86 scratch_pool); 87 88 notify->kind = kind; 89 notify->err = err; 90 91 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 92 } 93 94 return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err, 95 (kind == svn_node_dir 96 ? _("Directory '%s' is out of date") 97 : _("File '%s' is out of date")), 98 local_abspath 99 ? svn_dirent_local_style(local_abspath, 100 scratch_pool) 101 : svn_path_url_add_component2(base_url, 102 path, 103 scratch_pool)); 104 } 105 else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN) 106 || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH 107 || err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN 108 || err->apr_err == SVN_ERR_RA_NOT_LOCKED) 109 { 110 if (ctx->notify_func2) 111 { 112 svn_wc_notify_t *notify; 113 114 if (local_abspath) 115 notify = svn_wc_create_notify(local_abspath, 116 svn_wc_notify_failed_locked, 117 scratch_pool); 118 else 119 notify = svn_wc_create_notify_url( 120 svn_path_url_add_component2(base_url, path, 121 scratch_pool), 122 svn_wc_notify_failed_locked, 123 scratch_pool); 124 125 notify->kind = kind; 126 notify->err = err; 127 128 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 129 } 130 131 return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err, 132 (kind == svn_node_dir 133 ? _("Directory '%s' is locked in another working copy") 134 : _("File '%s' is locked in another working copy")), 135 local_abspath 136 ? svn_dirent_local_style(local_abspath, 137 scratch_pool) 138 : svn_path_url_add_component2(base_url, 139 path, 140 scratch_pool)); 141 } 142 else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN) 143 || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE) 144 { 145 if (ctx->notify_func2) 146 { 147 svn_wc_notify_t *notify; 148 149 if (local_abspath) 150 notify = svn_wc_create_notify( 151 local_abspath, 152 svn_wc_notify_failed_forbidden_by_server, 153 scratch_pool); 154 else 155 notify = svn_wc_create_notify_url( 156 svn_path_url_add_component2(base_url, path, 157 scratch_pool), 158 svn_wc_notify_failed_forbidden_by_server, 159 scratch_pool); 160 161 notify->kind = kind; 162 notify->err = err; 163 164 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 165 } 166 167 return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err, 168 (kind == svn_node_dir 169 ? _("Changing directory '%s' is forbidden by the server") 170 : _("Changing file '%s' is forbidden by the server")), 171 local_abspath 172 ? svn_dirent_local_style(local_abspath, 173 scratch_pool) 174 : svn_path_url_add_component2(base_url, 175 path, 176 scratch_pool)); 177 } 178 else 179 return err; 180} 181 182 183/*** Harvesting Commit Candidates ***/ 184 185 186/* Add a new commit candidate (described by all parameters except 187 `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's 188 members are allocated out of RESULT_POOL. 189 190 If the state flag specifies that a lock must be used, store the token in LOCK 191 in lock_tokens. 192 */ 193static svn_error_t * 194add_committable(svn_client__committables_t *committables, 195 const char *local_abspath, 196 svn_node_kind_t kind, 197 const char *repos_root_url, 198 const char *repos_relpath, 199 svn_revnum_t revision, 200 const char *copyfrom_relpath, 201 svn_revnum_t copyfrom_rev, 202 const char *moved_from_abspath, 203 apr_byte_t state_flags, 204 apr_hash_t *lock_tokens, 205 const svn_lock_t *lock, 206 apr_pool_t *result_pool, 207 apr_pool_t *scratch_pool) 208{ 209 apr_array_header_t *array; 210 svn_client_commit_item3_t *new_item; 211 212 /* Sanity checks. */ 213 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 214 SVN_ERR_ASSERT(repos_root_url && repos_relpath); 215 216 /* ### todo: Get the canonical repository for this item, which will 217 be the real key for the COMMITTABLES hash, instead of the above 218 bogosity. */ 219 array = svn_hash_gets(committables->by_repository, repos_root_url); 220 221 /* E-gads! There is no array for this repository yet! Oh, no 222 problem, we'll just create (and add to the hash) one. */ 223 if (array == NULL) 224 { 225 array = apr_array_make(result_pool, 1, sizeof(new_item)); 226 svn_hash_sets(committables->by_repository, 227 apr_pstrdup(result_pool, repos_root_url), array); 228 } 229 230 /* Now update pointer values, ensuring that their allocations live 231 in POOL. */ 232 new_item = svn_client_commit_item3_create(result_pool); 233 new_item->path = apr_pstrdup(result_pool, local_abspath); 234 new_item->kind = kind; 235 new_item->url = svn_path_url_add_component2(repos_root_url, 236 repos_relpath, 237 result_pool); 238 new_item->revision = revision; 239 new_item->copyfrom_url = copyfrom_relpath 240 ? svn_path_url_add_component2(repos_root_url, 241 copyfrom_relpath, 242 result_pool) 243 : NULL; 244 new_item->copyfrom_rev = copyfrom_rev; 245 new_item->state_flags = state_flags; 246 new_item->incoming_prop_changes = apr_array_make(result_pool, 1, 247 sizeof(svn_prop_t *)); 248 249 if (moved_from_abspath) 250 new_item->moved_from_abspath = apr_pstrdup(result_pool, 251 moved_from_abspath); 252 253 /* Now, add the commit item to the array. */ 254 APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item; 255 256 /* ... and to the hash. */ 257 svn_hash_sets(committables->by_path, new_item->path, new_item); 258 259 if (lock 260 && lock_tokens 261 && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)) 262 { 263 svn_hash_sets(lock_tokens, new_item->url, 264 apr_pstrdup(result_pool, lock->token)); 265 } 266 267 return SVN_NO_ERROR; 268} 269 270/* If there is a commit item for PATH in COMMITTABLES, return it, else 271 return NULL. Use POOL for temporary allocation only. */ 272static svn_client_commit_item3_t * 273look_up_committable(svn_client__committables_t *committables, 274 const char *path, 275 apr_pool_t *pool) 276{ 277 return (svn_client_commit_item3_t *) 278 svn_hash_gets(committables->by_path, path); 279} 280 281/* Helper function for svn_client__harvest_committables(). 282 * Determine whether we are within a tree-conflicted subtree of the 283 * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */ 284static svn_error_t * 285bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx, 286 const char *local_abspath, 287 svn_wc_notify_func2_t notify_func, 288 void *notify_baton, 289 apr_pool_t *scratch_pool) 290{ 291 const char *wcroot_abspath; 292 293 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath, 294 scratch_pool, scratch_pool)); 295 296 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 297 298 while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath)) 299 { 300 svn_boolean_t tree_conflicted; 301 302 /* Check if the parent has tree conflicts */ 303 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, 304 wc_ctx, local_abspath, scratch_pool)); 305 if (tree_conflicted) 306 { 307 if (notify_func != NULL) 308 { 309 notify_func(notify_baton, 310 svn_wc_create_notify(local_abspath, 311 svn_wc_notify_failed_conflict, 312 scratch_pool), 313 scratch_pool); 314 } 315 316 return svn_error_createf( 317 SVN_ERR_WC_FOUND_CONFLICT, NULL, 318 _("Aborting commit: '%s' remains in tree-conflict"), 319 svn_dirent_local_style(local_abspath, scratch_pool)); 320 } 321 322 /* Step outwards */ 323 if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) 324 break; 325 else 326 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 327 } 328 329 return SVN_NO_ERROR; 330} 331 332 333/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using 334 WC_CTX and add those candidates to COMMITTABLES. If in ADDS_ONLY modes, 335 only new additions are recognized. 336 337 DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH 338 when LOCAL_ABSPATH is itself a directory; see 339 svn_client__harvest_committables() for its behavior. 340 341 Lock tokens of candidates will be added to LOCK_TOKENS, if 342 non-NULL. JUST_LOCKED indicates whether to treat non-modified items with 343 lock tokens as commit candidates. 344 345 If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to 346 be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as 347 items to delete in the copy destination. COPY_MODE_ROOT should be set TRUE 348 for the first call for which COPY_MODE is TRUE, i.e. not for the 349 recursive calls, and FALSE otherwise. 350 351 If CHANGELISTS is non-NULL, it is a hash whose keys are const char * 352 changelist names used as a restrictive filter 353 when harvesting committables; that is, don't add a path to 354 COMMITTABLES unless it's a member of one of those changelists. 355 356 IS_EXPLICIT_TARGET should always be passed as TRUE, except when 357 harvest_committables() calls itself in recursion. This provides a way to 358 tell whether LOCAL_ABSPATH was an original target or whether it was reached 359 by recursing deeper into a dir target. (This is used to skip all file 360 externals that aren't explicit commit targets.) 361 362 DANGLERS is a hash table mapping const char* absolute paths of a parent 363 to a const char * absolute path of a child. See the comment about 364 danglers at the top of svn_client__harvest_committables(). 365 366 If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see 367 if the user has cancelled the operation. 368 369 Any items added to COMMITTABLES are allocated from the COMITTABLES 370 hash pool, not POOL. SCRATCH_POOL is used for temporary allocations. */ 371 372struct harvest_baton 373{ 374 /* Static data */ 375 const char *root_abspath; 376 svn_client__committables_t *committables; 377 apr_hash_t *lock_tokens; 378 const char *commit_relpath; /* Valid for the harvest root */ 379 svn_depth_t depth; 380 svn_boolean_t just_locked; 381 apr_hash_t *changelists; 382 apr_hash_t *danglers; 383 svn_client__check_url_kind_t check_url_func; 384 void *check_url_baton; 385 svn_wc_notify_func2_t notify_func; 386 void *notify_baton; 387 svn_wc_context_t *wc_ctx; 388 apr_pool_t *result_pool; 389 390 /* Harvester state */ 391 const char *skip_below_abspath; /* If non-NULL, skip everything below */ 392}; 393 394static svn_error_t * 395harvest_status_callback(void *status_baton, 396 const char *local_abspath, 397 const svn_wc_status3_t *status, 398 apr_pool_t *scratch_pool); 399 400static svn_error_t * 401harvest_committables(const char *local_abspath, 402 svn_client__committables_t *committables, 403 apr_hash_t *lock_tokens, 404 const char *copy_mode_relpath, 405 svn_depth_t depth, 406 svn_boolean_t just_locked, 407 apr_hash_t *changelists, 408 apr_hash_t *danglers, 409 svn_client__check_url_kind_t check_url_func, 410 void *check_url_baton, 411 svn_cancel_func_t cancel_func, 412 void *cancel_baton, 413 svn_wc_notify_func2_t notify_func, 414 void *notify_baton, 415 svn_wc_context_t *wc_ctx, 416 apr_pool_t *result_pool, 417 apr_pool_t *scratch_pool) 418{ 419 struct harvest_baton baton; 420 421 SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked); 422 423 baton.root_abspath = local_abspath; 424 baton.committables = committables; 425 baton.lock_tokens = lock_tokens; 426 baton.commit_relpath = copy_mode_relpath; 427 baton.depth = depth; 428 baton.just_locked = just_locked; 429 baton.changelists = changelists; 430 baton.danglers = danglers; 431 baton.check_url_func = check_url_func; 432 baton.check_url_baton = check_url_baton; 433 baton.notify_func = notify_func; 434 baton.notify_baton = notify_baton; 435 baton.wc_ctx = wc_ctx; 436 baton.result_pool = result_pool; 437 438 baton.skip_below_abspath = NULL; 439 440 SVN_ERR(svn_wc_walk_status(wc_ctx, 441 local_abspath, 442 depth, 443 (copy_mode_relpath != NULL) /* get_all */, 444 FALSE /* no_ignore */, 445 FALSE /* ignore_text_mods */, 446 NULL /* ignore_patterns */, 447 harvest_status_callback, 448 &baton, 449 cancel_func, cancel_baton, 450 scratch_pool)); 451 452 return SVN_NO_ERROR; 453} 454 455static svn_error_t * 456harvest_not_present_for_copy(svn_wc_context_t *wc_ctx, 457 const char *local_abspath, 458 svn_client__committables_t *committables, 459 const char *repos_root_url, 460 const char *commit_relpath, 461 svn_client__check_url_kind_t check_url_func, 462 void *check_url_baton, 463 apr_pool_t *result_pool, 464 apr_pool_t *scratch_pool) 465{ 466 const apr_array_header_t *children; 467 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 468 int i; 469 470 SVN_ERR_ASSERT(commit_relpath != NULL); 471 472 /* A function to retrieve not present children would be nice to have */ 473 SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx, 474 local_abspath, 475 scratch_pool, iterpool)); 476 477 for (i = 0; i < children->nelts; i++) 478 { 479 const char *this_abspath = APR_ARRAY_IDX(children, i, const char *); 480 const char *name = svn_dirent_basename(this_abspath, NULL); 481 const char *this_commit_relpath; 482 svn_boolean_t not_present; 483 svn_node_kind_t kind; 484 485 svn_pool_clear(iterpool); 486 487 SVN_ERR(svn_wc__node_is_not_present(¬_present, NULL, NULL, wc_ctx, 488 this_abspath, FALSE, scratch_pool)); 489 490 if (!not_present) 491 continue; /* Node is replaced */ 492 493 this_commit_relpath = svn_relpath_join(commit_relpath, name, 494 iterpool); 495 496 /* We should check if we should really add a delete operation */ 497 if (check_url_func) 498 { 499 svn_revnum_t parent_rev; 500 const char *parent_repos_relpath; 501 const char *parent_repos_root_url; 502 const char *node_url; 503 504 /* Determine from what parent we would be the deleted child */ 505 SVN_ERR(svn_wc__node_get_origin( 506 NULL, &parent_rev, &parent_repos_relpath, 507 &parent_repos_root_url, NULL, NULL, NULL, 508 wc_ctx, 509 svn_dirent_dirname(this_abspath, 510 scratch_pool), 511 FALSE, scratch_pool, scratch_pool)); 512 513 node_url = svn_path_url_add_component2( 514 svn_path_url_add_component2(parent_repos_root_url, 515 parent_repos_relpath, 516 scratch_pool), 517 svn_dirent_basename(this_abspath, NULL), 518 iterpool); 519 520 SVN_ERR(check_url_func(check_url_baton, &kind, 521 node_url, parent_rev, iterpool)); 522 523 if (kind == svn_node_none) 524 continue; /* This node can't be deleted */ 525 } 526 else 527 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath, 528 TRUE, TRUE, scratch_pool)); 529 530 SVN_ERR(add_committable(committables, this_abspath, kind, 531 repos_root_url, 532 this_commit_relpath, 533 SVN_INVALID_REVNUM, 534 NULL /* copyfrom_relpath */, 535 SVN_INVALID_REVNUM /* copyfrom_rev */, 536 NULL /* moved_from_abspath */, 537 SVN_CLIENT_COMMIT_ITEM_DELETE, 538 NULL, NULL, 539 result_pool, scratch_pool)); 540 } 541 542 svn_pool_destroy(iterpool); 543 return SVN_NO_ERROR; 544} 545 546/* Implements svn_wc_status_func4_t */ 547static svn_error_t * 548harvest_status_callback(void *status_baton, 549 const char *local_abspath, 550 const svn_wc_status3_t *status, 551 apr_pool_t *scratch_pool) 552{ 553 apr_byte_t state_flags = 0; 554 svn_revnum_t node_rev; 555 const char *cf_relpath = NULL; 556 svn_revnum_t cf_rev = SVN_INVALID_REVNUM; 557 svn_boolean_t matches_changelists; 558 svn_boolean_t is_added; 559 svn_boolean_t is_deleted; 560 svn_boolean_t is_replaced; 561 svn_boolean_t is_op_root; 562 svn_revnum_t original_rev; 563 const char *original_relpath; 564 svn_boolean_t copy_mode; 565 566 struct harvest_baton *baton = status_baton; 567 svn_boolean_t is_harvest_root = 568 (strcmp(baton->root_abspath, local_abspath) == 0); 569 svn_client__committables_t *committables = baton->committables; 570 const char *repos_root_url = status->repos_root_url; 571 const char *commit_relpath = NULL; 572 svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root); 573 svn_boolean_t just_locked = baton->just_locked; 574 apr_hash_t *changelists = baton->changelists; 575 svn_wc_notify_func2_t notify_func = baton->notify_func; 576 void *notify_baton = baton->notify_baton; 577 svn_wc_context_t *wc_ctx = baton->wc_ctx; 578 apr_pool_t *result_pool = baton->result_pool; 579 const char *moved_from_abspath = NULL; 580 581 if (baton->commit_relpath) 582 commit_relpath = svn_relpath_join( 583 baton->commit_relpath, 584 svn_dirent_skip_ancestor(baton->root_abspath, 585 local_abspath), 586 scratch_pool); 587 588 copy_mode = (commit_relpath != NULL); 589 590 if (baton->skip_below_abspath 591 && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath)) 592 { 593 return SVN_NO_ERROR; 594 } 595 else 596 baton->skip_below_abspath = NULL; /* We have left the skip tree */ 597 598 /* Return early for nodes that don't have a committable status */ 599 switch (status->node_status) 600 { 601 case svn_wc_status_unversioned: 602 case svn_wc_status_ignored: 603 case svn_wc_status_external: 604 case svn_wc_status_none: 605 /* Unversioned nodes aren't committable, but are reported by the status 606 walker. 607 But if the unversioned node is the root of the walk, we have a user 608 error */ 609 if (is_harvest_root) 610 return svn_error_createf( 611 SVN_ERR_ILLEGAL_TARGET, NULL, 612 _("'%s' is not under version control"), 613 svn_dirent_local_style(local_abspath, scratch_pool)); 614 return SVN_NO_ERROR; 615 case svn_wc_status_normal: 616 /* Status normal nodes aren't modified, so we don't have to commit them 617 when we perform a normal commit. But if a node is conflicted we want 618 to stop the commit and if we are collecting lock tokens we want to 619 look further anyway. 620 621 When in copy mode we need to compare the revision of the node against 622 the parent node to copy mixed-revision base nodes properly */ 623 if (!copy_mode && !status->conflicted 624 && !(just_locked && status->lock)) 625 return SVN_NO_ERROR; 626 break; 627 default: 628 /* Fall through */ 629 break; 630 } 631 632 /* Early out if the item is already marked as committable. */ 633 if (look_up_committable(committables, local_abspath, scratch_pool)) 634 return SVN_NO_ERROR; 635 636 SVN_ERR_ASSERT((copy_mode && commit_relpath) 637 || (! copy_mode && ! commit_relpath)); 638 SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root); 639 640 /* Save the result for reuse. */ 641 matches_changelists = ((changelists == NULL) 642 || (status->changelist != NULL 643 && svn_hash_gets(changelists, status->changelist) 644 != NULL)); 645 646 /* Early exit. */ 647 if (status->kind != svn_node_dir && ! matches_changelists) 648 { 649 return SVN_NO_ERROR; 650 } 651 652 /* If NODE is in our changelist, then examine it for conflicts. We 653 need to bail out if any conflicts exist. 654 The status walker checked for conflict marker removal. */ 655 if (status->conflicted && matches_changelists) 656 { 657 if (notify_func != NULL) 658 { 659 notify_func(notify_baton, 660 svn_wc_create_notify(local_abspath, 661 svn_wc_notify_failed_conflict, 662 scratch_pool), 663 scratch_pool); 664 } 665 666 return svn_error_createf( 667 SVN_ERR_WC_FOUND_CONFLICT, NULL, 668 _("Aborting commit: '%s' remains in conflict"), 669 svn_dirent_local_style(local_abspath, scratch_pool)); 670 } 671 else if (status->node_status == svn_wc_status_obstructed) 672 { 673 /* A node's type has changed before attempting to commit. 674 This also catches symlink vs non symlink changes */ 675 676 if (notify_func != NULL) 677 { 678 notify_func(notify_baton, 679 svn_wc_create_notify(local_abspath, 680 svn_wc_notify_failed_obstruction, 681 scratch_pool), 682 scratch_pool); 683 } 684 685 return svn_error_createf( 686 SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 687 _("Node '%s' has unexpectedly changed kind"), 688 svn_dirent_local_style(local_abspath, scratch_pool)); 689 } 690 691 if (status->conflicted && status->kind == svn_node_unknown) 692 return SVN_NO_ERROR; /* Ignore delete-delete conflict */ 693 694 /* Return error on unknown path kinds. We check both the entry and 695 the node itself, since a path might have changed kind since its 696 entry was written. */ 697 SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted, 698 &is_replaced, 699 &is_op_root, 700 &node_rev, 701 &original_rev, &original_relpath, 702 wc_ctx, local_abspath, 703 scratch_pool, scratch_pool)); 704 705 /* Hande file externals only when passed as explicit target. Note that 706 * svn_client_commit6() passes all committable externals in as explicit 707 * targets iff they count. */ 708 if (status->file_external && !is_harvest_root) 709 { 710 return SVN_NO_ERROR; 711 } 712 713 if (status->node_status == svn_wc_status_missing && matches_changelists) 714 { 715 /* Added files and directories must exist. See issue #3198. */ 716 if (is_added && is_op_root) 717 { 718 if (notify_func != NULL) 719 { 720 notify_func(notify_baton, 721 svn_wc_create_notify(local_abspath, 722 svn_wc_notify_failed_missing, 723 scratch_pool), 724 scratch_pool); 725 } 726 return svn_error_createf( 727 SVN_ERR_WC_PATH_NOT_FOUND, NULL, 728 _("'%s' is scheduled for addition, but is missing"), 729 svn_dirent_local_style(local_abspath, scratch_pool)); 730 } 731 732 return SVN_NO_ERROR; 733 } 734 735 if (is_deleted && !is_op_root /* && !is_added */) 736 return SVN_NO_ERROR; /* Not an operational delete and not an add. */ 737 738 /* Check for the deletion case. 739 * We delete explicitly deleted nodes (duh!) 740 * We delete not-present children of copies 741 * We delete nodes that directly replace a node in its ancestor 742 */ 743 744 if (is_deleted || is_replaced) 745 state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 746 747 /* Check for adds and copies */ 748 if (is_added && is_op_root) 749 { 750 /* Root of local add or copy */ 751 state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD; 752 753 if (original_relpath) 754 { 755 /* Root of copy */ 756 state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY; 757 cf_relpath = original_relpath; 758 cf_rev = original_rev; 759 760 if (status->moved_from_abspath && !copy_mode) 761 { 762 state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE; 763 moved_from_abspath = status->moved_from_abspath; 764 } 765 } 766 } 767 768 /* Further copies may occur in copy mode. */ 769 else if (copy_mode 770 && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)) 771 { 772 svn_revnum_t dir_rev = SVN_INVALID_REVNUM; 773 const char *dir_repos_relpath = NULL; 774 775 if (!copy_mode_root && !is_added) 776 SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, &dir_repos_relpath, NULL, 777 NULL, NULL, 778 wc_ctx, svn_dirent_dirname(local_abspath, 779 scratch_pool), 780 FALSE /* ignore_enoent */, 781 scratch_pool, scratch_pool)); 782 783 if (copy_mode_root || status->switched || node_rev != dir_rev) 784 { 785 state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD 786 | SVN_CLIENT_COMMIT_ITEM_IS_COPY); 787 788 if (status->copied) 789 { 790 /* Copy from original location */ 791 cf_rev = original_rev; 792 cf_relpath = original_relpath; 793 } 794 else 795 { 796 /* Copy BASE location, to represent a mixed-rev or switch copy */ 797 cf_rev = status->revision; 798 cf_relpath = status->repos_relpath; 799 } 800 801 if (!copy_mode_root && !is_added && baton->check_url_func 802 && dir_repos_relpath) 803 { 804 svn_node_kind_t me_kind; 805 /* Maybe we need to issue an delete (mixed rev/switched) */ 806 807 SVN_ERR(baton->check_url_func( 808 baton->check_url_baton, &me_kind, 809 svn_path_url_add_component2(repos_root_url, 810 svn_relpath_join(dir_repos_relpath, 811 svn_dirent_basename(local_abspath, 812 NULL), 813 scratch_pool), 814 scratch_pool), 815 dir_rev, scratch_pool)); 816 if (me_kind != svn_node_none) 817 state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 818 } 819 } 820 } 821 822 if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 823 || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 824 { 825 svn_boolean_t text_mod = FALSE; 826 svn_boolean_t prop_mod = FALSE; 827 828 if (status->kind == svn_node_file) 829 { 830 /* Check for text modifications on files */ 831 if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 832 && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 833 { 834 text_mod = TRUE; /* Local added files are always modified */ 835 } 836 else 837 text_mod = (status->text_status != svn_wc_status_normal); 838 } 839 840 prop_mod = (status->prop_status != svn_wc_status_normal 841 && status->prop_status != svn_wc_status_none); 842 843 /* Set text/prop modification flags accordingly. */ 844 if (text_mod) 845 state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS; 846 if (prop_mod) 847 state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 848 } 849 850 /* If the entry has a lock token and it is already a commit candidate, 851 or the caller wants unmodified locked items to be treated as 852 such, note this fact. */ 853 if (status->lock && baton->lock_tokens && (state_flags || just_locked)) 854 { 855 state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN; 856 } 857 858 /* Now, if this is something to commit, add it to our list. */ 859 if (matches_changelists 860 && state_flags) 861 { 862 /* Finally, add the committable item. */ 863 SVN_ERR(add_committable(committables, local_abspath, 864 status->kind, 865 repos_root_url, 866 copy_mode 867 ? commit_relpath 868 : status->repos_relpath, 869 copy_mode 870 ? SVN_INVALID_REVNUM 871 : node_rev, 872 cf_relpath, 873 cf_rev, 874 moved_from_abspath, 875 state_flags, 876 baton->lock_tokens, status->lock, 877 result_pool, scratch_pool)); 878 } 879 880 /* Fetch lock tokens for descendants of deleted BASE nodes. */ 881 if (matches_changelists 882 && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 883 && !copy_mode 884 && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */ 885 && baton->lock_tokens) 886 { 887 apr_hash_t *local_relpath_tokens; 888 apr_hash_index_t *hi; 889 890 SVN_ERR(svn_wc__node_get_lock_tokens_recursive( 891 &local_relpath_tokens, wc_ctx, local_abspath, 892 result_pool, scratch_pool)); 893 894 /* Add tokens to existing hash. */ 895 for (hi = apr_hash_first(scratch_pool, local_relpath_tokens); 896 hi; 897 hi = apr_hash_next(hi)) 898 { 899 const void *key; 900 apr_ssize_t klen; 901 void * val; 902 903 apr_hash_this(hi, &key, &klen, &val); 904 905 apr_hash_set(baton->lock_tokens, key, klen, val); 906 } 907 } 908 909 /* Make sure we check for dangling children on additions 910 911 We perform this operation on the harvest root, and on roots caused by 912 changelist filtering. 913 */ 914 if (matches_changelists 915 && (is_harvest_root || baton->changelists) 916 && state_flags 917 && (is_added || (is_deleted && is_op_root && status->copied)) 918 && baton->danglers) 919 { 920 /* If a node is added, its parent must exist in the repository at the 921 time of committing */ 922 apr_hash_t *danglers = baton->danglers; 923 svn_boolean_t parent_added; 924 const char *parent_abspath = svn_dirent_dirname(local_abspath, 925 scratch_pool); 926 927 /* First check if parent is already in the list of commits 928 (Common case for GUI clients that provide a list of commit targets) */ 929 if (look_up_committable(committables, parent_abspath, scratch_pool)) 930 parent_added = FALSE; /* Skip all expensive checks */ 931 else 932 SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath, 933 scratch_pool)); 934 935 if (parent_added) 936 { 937 const char *copy_root_abspath; 938 svn_boolean_t parent_is_copy; 939 940 /* The parent is added, so either it is a copy, or a locally added 941 * directory. In either case, we require the op-root of the parent 942 * to be part of the commit. See issue #4059. */ 943 SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL, 944 NULL, NULL, ©_root_abspath, 945 wc_ctx, parent_abspath, 946 FALSE, scratch_pool, scratch_pool)); 947 948 if (parent_is_copy) 949 parent_abspath = copy_root_abspath; 950 951 if (!svn_hash_gets(danglers, parent_abspath)) 952 { 953 svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath), 954 apr_pstrdup(result_pool, local_abspath)); 955 } 956 } 957 } 958 959 if (is_deleted && !is_added) 960 { 961 /* Skip all descendants */ 962 if (status->kind == svn_node_dir) 963 baton->skip_below_abspath = apr_pstrdup(baton->result_pool, 964 local_abspath); 965 return SVN_NO_ERROR; 966 } 967 968 /* Recursively handle each node according to depth, except when the 969 node is only being deleted, or is in an added tree (as added trees 970 use the normal commit handling). */ 971 if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir) 972 { 973 SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables, 974 repos_root_url, commit_relpath, 975 baton->check_url_func, 976 baton->check_url_baton, 977 result_pool, scratch_pool)); 978 } 979 980 return SVN_NO_ERROR; 981} 982 983/* Baton for handle_descendants */ 984struct handle_descendants_baton 985{ 986 svn_wc_context_t *wc_ctx; 987 svn_cancel_func_t cancel_func; 988 void *cancel_baton; 989 svn_client__check_url_kind_t check_url_func; 990 void *check_url_baton; 991 svn_client__committables_t *committables; 992}; 993 994/* Helper for the commit harvesters */ 995static svn_error_t * 996handle_descendants(void *baton, 997 const void *key, apr_ssize_t klen, void *val, 998 apr_pool_t *pool) 999{ 1000 struct handle_descendants_baton *hdb = baton; 1001 apr_array_header_t *commit_items = val; 1002 apr_pool_t *iterpool = svn_pool_create(pool); 1003 const char *repos_root_url = key; 1004 int i; 1005 1006 for (i = 0; i < commit_items->nelts; i++) 1007 { 1008 svn_client_commit_item3_t *item = 1009 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1010 const apr_array_header_t *absent_descendants; 1011 int j; 1012 1013 /* Is this a copy operation? */ 1014 if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1015 || ! item->copyfrom_url) 1016 continue; 1017 1018 if (hdb->cancel_func) 1019 SVN_ERR(hdb->cancel_func(hdb->cancel_baton)); 1020 1021 svn_pool_clear(iterpool); 1022 1023 SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants, 1024 hdb->wc_ctx, item->path, 1025 iterpool, iterpool)); 1026 1027 for (j = 0; j < absent_descendants->nelts; j++) 1028 { 1029 svn_node_kind_t kind; 1030 svn_client_commit_item3_t *desc_item; 1031 const char *relpath = APR_ARRAY_IDX(absent_descendants, j, 1032 const char *); 1033 const char *local_abspath = svn_dirent_join(item->path, relpath, 1034 iterpool); 1035 1036 /* ### Need a sub-iterpool? */ 1037 1038 1039 /* We found a 'not present' descendant during a copy (at op_depth>0), 1040 this is most commonly caused by copying some mixed revision tree. 1041 1042 In this case not present can imply that the node does not exist 1043 in the parent revision, or that the node does. But we want to copy 1044 the working copy state in which it does not exist, but might be 1045 replaced. */ 1046 1047 desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath); 1048 1049 /* If the path has a commit operation (possibly at an higher 1050 op_depth, we might want to turn an add in a replace. */ 1051 if (desc_item) 1052 { 1053 const char *dir; 1054 svn_boolean_t found_intermediate = FALSE; 1055 1056 if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1057 continue; /* We already have a delete or replace */ 1058 else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1059 continue; /* Not a copy/add, just a modification */ 1060 1061 dir = svn_dirent_dirname(local_abspath, iterpool); 1062 1063 while (strcmp(dir, item->path)) 1064 { 1065 svn_client_commit_item3_t *i_item; 1066 1067 i_item = svn_hash_gets(hdb->committables->by_path, dir); 1068 1069 if (i_item) 1070 { 1071 if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1072 || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1073 { 1074 found_intermediate = TRUE; 1075 break; 1076 } 1077 } 1078 dir = svn_dirent_dirname(dir, iterpool); 1079 } 1080 1081 if (found_intermediate) 1082 continue; /* Some intermediate ancestor is an add or delete */ 1083 1084 /* Fall through to detect if we need to turn the add in a 1085 replace. */ 1086 } 1087 1088 if (hdb->check_url_func) 1089 { 1090 const char *from_url = svn_path_url_add_component2( 1091 item->copyfrom_url, relpath, 1092 iterpool); 1093 1094 SVN_ERR(hdb->check_url_func(hdb->check_url_baton, 1095 &kind, from_url, item->copyfrom_rev, 1096 iterpool)); 1097 1098 if (kind == svn_node_none) 1099 continue; /* This node is already deleted */ 1100 } 1101 else 1102 kind = svn_node_unknown; /* 'Ok' for a delete of something */ 1103 1104 if (desc_item) 1105 { 1106 /* Extend the existing add/copy item to create a replace */ 1107 desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 1108 continue; 1109 } 1110 1111 /* Add a new commit item that describes the delete */ 1112 1113 SVN_ERR(add_committable(hdb->committables, 1114 svn_dirent_join(item->path, relpath, 1115 iterpool), 1116 kind, 1117 repos_root_url, 1118 svn_uri_skip_ancestor( 1119 repos_root_url, 1120 svn_path_url_add_component2(item->url, 1121 relpath, 1122 iterpool), 1123 iterpool), 1124 SVN_INVALID_REVNUM, 1125 NULL /* copyfrom_relpath */, 1126 SVN_INVALID_REVNUM, 1127 NULL /* moved_from_abspath */, 1128 SVN_CLIENT_COMMIT_ITEM_DELETE, 1129 NULL /* lock tokens */, 1130 NULL /* lock */, 1131 commit_items->pool, 1132 iterpool)); 1133 } 1134 } 1135 1136 svn_pool_destroy(iterpool); 1137 return SVN_NO_ERROR; 1138} 1139 1140/* Allocate and initialize the COMMITTABLES structure from POOL. 1141 */ 1142static void 1143create_committables(svn_client__committables_t **committables, 1144 apr_pool_t *pool) 1145{ 1146 *committables = apr_palloc(pool, sizeof(**committables)); 1147 1148 (*committables)->by_repository = apr_hash_make(pool); 1149 (*committables)->by_path = apr_hash_make(pool); 1150} 1151 1152svn_error_t * 1153svn_client__harvest_committables(svn_client__committables_t **committables, 1154 apr_hash_t **lock_tokens, 1155 const char *base_dir_abspath, 1156 const apr_array_header_t *targets, 1157 int depth_empty_start, 1158 svn_depth_t depth, 1159 svn_boolean_t just_locked, 1160 const apr_array_header_t *changelists, 1161 svn_client__check_url_kind_t check_url_func, 1162 void *check_url_baton, 1163 svn_client_ctx_t *ctx, 1164 apr_pool_t *result_pool, 1165 apr_pool_t *scratch_pool) 1166{ 1167 int i; 1168 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1169 apr_hash_t *changelist_hash = NULL; 1170 struct handle_descendants_baton hdb; 1171 apr_hash_index_t *hi; 1172 1173 /* It's possible that one of the named targets has a parent that is 1174 * itself scheduled for addition or replacement -- that is, the 1175 * parent is not yet versioned in the repository. This is okay, as 1176 * long as the parent itself is part of this same commit, either 1177 * directly, or by virtue of a grandparent, great-grandparent, etc, 1178 * being part of the commit. 1179 * 1180 * Since we don't know what's included in the commit until we've 1181 * harvested all the targets, we can't reliably check this as we 1182 * go. So in `danglers', we record named targets whose parents 1183 * do not yet exist in the repository. Then after harvesting the total 1184 * commit group, we check to make sure those parents are included. 1185 * 1186 * Each key of danglers is a parent which does not exist in the 1187 * repository. The (const char *) value is one of that parent's 1188 * children which is named as part of the commit; the child is 1189 * included only to make a better error message. 1190 * 1191 * (The reason we don't bother to check unnamed -- i.e, implicit -- 1192 * targets is that they can only join the commit if their parents 1193 * did too, so this situation can't arise for them.) 1194 */ 1195 apr_hash_t *danglers = apr_hash_make(scratch_pool); 1196 1197 SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath)); 1198 1199 /* Create the COMMITTABLES structure. */ 1200 create_committables(committables, result_pool); 1201 1202 /* And the LOCK_TOKENS dito. */ 1203 *lock_tokens = apr_hash_make(result_pool); 1204 1205 /* If we have a list of changelists, convert that into a hash with 1206 changelist keys. */ 1207 if (changelists && changelists->nelts) 1208 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, 1209 scratch_pool)); 1210 1211 for (i = 0; i < targets->nelts; ++i) 1212 { 1213 const char *target_abspath; 1214 1215 svn_pool_clear(iterpool); 1216 1217 /* Add the relative portion to the base abspath. */ 1218 target_abspath = svn_dirent_join(base_dir_abspath, 1219 APR_ARRAY_IDX(targets, i, const char *), 1220 iterpool); 1221 1222 /* Handle our TARGET. */ 1223 /* Make sure this isn't inside a working copy subtree that is 1224 * marked as tree-conflicted. */ 1225 SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath, 1226 ctx->notify_func2, 1227 ctx->notify_baton2, 1228 iterpool)); 1229 1230 /* Are the remaining items externals with depth empty? */ 1231 if (i == depth_empty_start) 1232 depth = svn_depth_empty; 1233 1234 SVN_ERR(harvest_committables(target_abspath, 1235 *committables, *lock_tokens, 1236 NULL /* COPY_MODE_RELPATH */, 1237 depth, just_locked, changelist_hash, 1238 danglers, 1239 check_url_func, check_url_baton, 1240 ctx->cancel_func, ctx->cancel_baton, 1241 ctx->notify_func2, ctx->notify_baton2, 1242 ctx->wc_ctx, result_pool, iterpool)); 1243 } 1244 1245 hdb.wc_ctx = ctx->wc_ctx; 1246 hdb.cancel_func = ctx->cancel_func; 1247 hdb.cancel_baton = ctx->cancel_baton; 1248 hdb.check_url_func = check_url_func; 1249 hdb.check_url_baton = check_url_baton; 1250 hdb.committables = *committables; 1251 1252 SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository, 1253 handle_descendants, &hdb, iterpool)); 1254 1255 /* Make sure that every path in danglers is part of the commit. */ 1256 for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi)) 1257 { 1258 const char *dangling_parent = apr_hash_this_key(hi); 1259 1260 svn_pool_clear(iterpool); 1261 1262 if (! look_up_committable(*committables, dangling_parent, iterpool)) 1263 { 1264 const char *dangling_child = apr_hash_this_val(hi); 1265 1266 if (ctx->notify_func2 != NULL) 1267 { 1268 svn_wc_notify_t *notify; 1269 1270 notify = svn_wc_create_notify(dangling_child, 1271 svn_wc_notify_failed_no_parent, 1272 scratch_pool); 1273 1274 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1275 } 1276 1277 return svn_error_createf( 1278 SVN_ERR_ILLEGAL_TARGET, NULL, 1279 _("'%s' is not known to exist in the repository " 1280 "and is not part of the commit, " 1281 "yet its child '%s' is part of the commit"), 1282 /* Probably one or both of these is an entry, but 1283 safest to local_stylize just in case. */ 1284 svn_dirent_local_style(dangling_parent, iterpool), 1285 svn_dirent_local_style(dangling_child, iterpool)); 1286 } 1287 } 1288 1289 svn_pool_destroy(iterpool); 1290 1291 return SVN_NO_ERROR; 1292} 1293 1294struct copy_committables_baton 1295{ 1296 svn_client_ctx_t *ctx; 1297 svn_client__committables_t *committables; 1298 apr_pool_t *result_pool; 1299 svn_client__check_url_kind_t check_url_func; 1300 void *check_url_baton; 1301}; 1302 1303static svn_error_t * 1304harvest_copy_committables(void *baton, void *item, apr_pool_t *pool) 1305{ 1306 struct copy_committables_baton *btn = baton; 1307 svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item; 1308 const char *repos_root_url; 1309 const char *commit_relpath; 1310 struct handle_descendants_baton hdb; 1311 1312 /* Read the entry for this SRC. */ 1313 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 1314 1315 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL, 1316 btn->ctx->wc_ctx, 1317 pair->src_abspath_or_url, 1318 pool, pool)); 1319 1320 commit_relpath = svn_uri_skip_ancestor(repos_root_url, 1321 pair->dst_abspath_or_url, pool); 1322 1323 /* Handle this SRC. */ 1324 SVN_ERR(harvest_committables(pair->src_abspath_or_url, 1325 btn->committables, NULL, 1326 commit_relpath, 1327 svn_depth_infinity, 1328 FALSE, /* JUST_LOCKED */ 1329 NULL /* changelists */, 1330 NULL, 1331 btn->check_url_func, 1332 btn->check_url_baton, 1333 btn->ctx->cancel_func, 1334 btn->ctx->cancel_baton, 1335 btn->ctx->notify_func2, 1336 btn->ctx->notify_baton2, 1337 btn->ctx->wc_ctx, btn->result_pool, pool)); 1338 1339 hdb.wc_ctx = btn->ctx->wc_ctx; 1340 hdb.cancel_func = btn->ctx->cancel_func; 1341 hdb.cancel_baton = btn->ctx->cancel_baton; 1342 hdb.check_url_func = btn->check_url_func; 1343 hdb.check_url_baton = btn->check_url_baton; 1344 hdb.committables = btn->committables; 1345 1346 SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository, 1347 handle_descendants, &hdb, pool)); 1348 1349 return SVN_NO_ERROR; 1350} 1351 1352 1353 1354svn_error_t * 1355svn_client__get_copy_committables(svn_client__committables_t **committables, 1356 const apr_array_header_t *copy_pairs, 1357 svn_client__check_url_kind_t check_url_func, 1358 void *check_url_baton, 1359 svn_client_ctx_t *ctx, 1360 apr_pool_t *result_pool, 1361 apr_pool_t *scratch_pool) 1362{ 1363 struct copy_committables_baton btn; 1364 1365 /* Create the COMMITTABLES structure. */ 1366 create_committables(committables, result_pool); 1367 1368 btn.ctx = ctx; 1369 btn.committables = *committables; 1370 btn.result_pool = result_pool; 1371 1372 btn.check_url_func = check_url_func; 1373 btn.check_url_baton = check_url_baton; 1374 1375 /* For each copy pair, harvest the committables for that pair into the 1376 committables hash. */ 1377 return svn_iter_apr_array(NULL, copy_pairs, 1378 harvest_copy_committables, &btn, scratch_pool); 1379} 1380 1381 1382/* A svn_sort__array()/qsort()-compatible sort routine for sorting 1383 an array of svn_client_commit_item_t *'s by their URL member. */ 1384static int 1385sort_commit_item_urls(const void *a, const void *b) 1386{ 1387 const svn_client_commit_item3_t *item1 1388 = *((const svn_client_commit_item3_t * const *) a); 1389 const svn_client_commit_item3_t *item2 1390 = *((const svn_client_commit_item3_t * const *) b); 1391 return svn_path_compare_paths(item1->url, item2->url); 1392} 1393 1394 1395svn_error_t * 1396svn_client__condense_commit_items2(const char *base_url, 1397 apr_array_header_t *commit_items, 1398 apr_pool_t *pool) 1399{ 1400 apr_array_header_t *ci = commit_items; /* convenience */ 1401 int i; 1402 1403 /* Sort our commit items by their URLs. */ 1404 svn_sort__array(ci, sort_commit_item_urls); 1405 1406 /* Hack BASE_URL off each URL; store the result as session_relpath. */ 1407 for (i = 0; i < ci->nelts; i++) 1408 { 1409 svn_client_commit_item3_t *this_item 1410 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1411 1412 this_item->session_relpath = svn_uri_skip_ancestor(base_url, 1413 this_item->url, pool); 1414 } 1415 1416 return SVN_NO_ERROR; 1417} 1418 1419svn_error_t * 1420svn_client__condense_commit_items(const char **base_url, 1421 apr_array_header_t *commit_items, 1422 apr_pool_t *pool) 1423{ 1424 apr_array_header_t *ci = commit_items; /* convenience */ 1425 const char *url; 1426 svn_client_commit_item3_t *item, *last_item = NULL; 1427 int i; 1428 1429 SVN_ERR_ASSERT(ci && ci->nelts); 1430 1431 /* Sort our commit items by their URLs. */ 1432 svn_sort__array(ci, sort_commit_item_urls); 1433 1434 /* Loop through the URLs, finding the longest usable ancestor common 1435 to all of them, and making sure there are no duplicate URLs. */ 1436 for (i = 0; i < ci->nelts; i++) 1437 { 1438 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1439 url = item->url; 1440 1441 if ((last_item) && (strcmp(last_item->url, url) == 0)) 1442 return svn_error_createf 1443 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL, 1444 _("Cannot commit both '%s' and '%s' as they refer to the same URL"), 1445 svn_dirent_local_style(item->path, pool), 1446 svn_dirent_local_style(last_item->path, pool)); 1447 1448 /* In the first iteration, our BASE_URL is just our only 1449 encountered commit URL to date. After that, we find the 1450 longest ancestor between the current BASE_URL and the current 1451 commit URL. */ 1452 if (i == 0) 1453 *base_url = apr_pstrdup(pool, url); 1454 else 1455 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool); 1456 1457 /* If our BASE_URL is itself a to-be-committed item, and it is 1458 anything other than an already-versioned directory with 1459 property mods, we'll call its parent directory URL the 1460 BASE_URL. Why? Because we can't have a file URL as our base 1461 -- period -- and all other directory operations (removal, 1462 addition, etc.) require that we open that directory's parent 1463 dir first. */ 1464 /* ### I don't understand the strlen()s here, hmmm. -kff */ 1465 if ((strlen(*base_url) == strlen(url)) 1466 && (! ((item->kind == svn_node_dir) 1467 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS))) 1468 *base_url = svn_uri_dirname(*base_url, pool); 1469 1470 /* Stash our item here for the next iteration. */ 1471 last_item = item; 1472 } 1473 1474 /* Now that we've settled on a *BASE_URL, go hack that base off 1475 of all of our URLs and store it as session_relpath. */ 1476 for (i = 0; i < ci->nelts; i++) 1477 { 1478 svn_client_commit_item3_t *this_item 1479 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1480 1481 this_item->session_relpath = svn_uri_skip_ancestor(*base_url, 1482 this_item->url, pool); 1483 } 1484#ifdef SVN_CLIENT_COMMIT_DEBUG 1485 /* ### TEMPORARY CODE ### */ 1486 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url)); 1487 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n")); 1488 for (i = 0; i < ci->nelts; i++) 1489 { 1490 svn_client_commit_item3_t *this_item 1491 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1492 char flags[6]; 1493 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1494 ? 'a' : '-'; 1495 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1496 ? 'd' : '-'; 1497 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1498 ? 't' : '-'; 1499 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1500 ? 'p' : '-'; 1501 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1502 ? 'c' : '-'; 1503 flags[5] = '\0'; 1504 SVN_DBG((" %s %6ld '%s' (%s)\n", 1505 flags, 1506 this_item->revision, 1507 this_item->url ? this_item->url : "", 1508 this_item->copyfrom_url ? this_item->copyfrom_url : "none")); 1509 } 1510#endif /* SVN_CLIENT_COMMIT_DEBUG */ 1511 1512 return SVN_NO_ERROR; 1513} 1514 1515 1516struct file_mod_t 1517{ 1518 const svn_client_commit_item3_t *item; 1519 void *file_baton; 1520 apr_pool_t *file_pool; 1521}; 1522 1523 1524/* A baton for use while driving a path-based editor driver for commit */ 1525struct item_commit_baton 1526{ 1527 apr_hash_t *file_mods; /* hash: path->file_mod_t */ 1528 const char *notify_path_prefix; /* notification path prefix 1529 (NULL is okay, else abs path) */ 1530 svn_client_ctx_t *ctx; /* client context baton */ 1531 apr_hash_t *commit_items; /* the committables */ 1532 const char *base_url; /* The session url for the commit */ 1533}; 1534 1535 1536/* Drive CALLBACK_BATON->editor with the change described by the item in 1537 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change 1538 * includes a text mod, however, call the editor's file_open() function 1539 * but do not send the text mod to the editor; instead, add a mapping of 1540 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods. 1541 * 1542 * Before driving the editor, call the cancellation and notification 1543 * callbacks in CALLBACK_BATON->ctx, if present. 1544 * 1545 * This implements svn_delta_path_driver_cb_func_t. */ 1546static svn_error_t * 1547do_item_commit(void **dir_baton, 1548 const svn_delta_editor_t *editor, 1549 void *edit_baton, 1550 void *parent_baton, 1551 void *callback_baton, 1552 const char *path, 1553 apr_pool_t *pool) 1554{ 1555 struct item_commit_baton *icb = callback_baton; 1556 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items, 1557 path); 1558 svn_node_kind_t kind = item->kind; 1559 void *file_baton = NULL; 1560 apr_pool_t *file_pool = NULL; 1561 apr_hash_t *file_mods = icb->file_mods; 1562 svn_client_ctx_t *ctx = icb->ctx; 1563 svn_error_t *err; 1564 const char *local_abspath = NULL; 1565 1566 /* Do some initializations. */ 1567 *dir_baton = NULL; 1568 if (item->kind != svn_node_none && item->path) 1569 { 1570 /* We always get an absolute path, see svn_client_commit_item3_t. */ 1571 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); 1572 local_abspath = item->path; 1573 } 1574 1575 /* If this is a file with textual mods, we'll be keeping its baton 1576 around until the end of the commit. So just lump its memory into 1577 a single, big, all-the-file-batons-in-here pool. Otherwise, we 1578 can just use POOL, and trust our caller to clean that mess up. */ 1579 if ((kind == svn_node_file) 1580 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1581 file_pool = apr_hash_pool_get(file_mods); 1582 else 1583 file_pool = pool; 1584 1585 /* Subpools are cheap, but memory isn't */ 1586 file_pool = svn_pool_create(file_pool); 1587 1588 /* Call the cancellation function. */ 1589 if (ctx->cancel_func) 1590 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1591 1592 /* Validation. */ 1593 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1594 { 1595 if (! item->copyfrom_url) 1596 return svn_error_createf 1597 (SVN_ERR_BAD_URL, NULL, 1598 _("Commit item '%s' has copy flag but no copyfrom URL"), 1599 svn_dirent_local_style(path, pool)); 1600 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev)) 1601 return svn_error_createf 1602 (SVN_ERR_CLIENT_BAD_REVISION, NULL, 1603 _("Commit item '%s' has copy flag but an invalid revision"), 1604 svn_dirent_local_style(path, pool)); 1605 } 1606 1607 /* If a feedback table was supplied by the application layer, 1608 describe what we're about to do to this item. */ 1609 if (ctx->notify_func2 && item->path) 1610 { 1611 const char *npath = item->path; 1612 svn_wc_notify_t *notify; 1613 1614 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1615 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1616 { 1617 /* We don't print the "(bin)" notice for binary files when 1618 replacing, only when adding. So we don't bother to get 1619 the mime-type here. */ 1620 if (item->copyfrom_url) 1621 notify = svn_wc_create_notify(npath, 1622 svn_wc_notify_commit_copied_replaced, 1623 pool); 1624 else 1625 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced, 1626 pool); 1627 1628 } 1629 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1630 { 1631 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted, 1632 pool); 1633 } 1634 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1635 { 1636 if (item->copyfrom_url) 1637 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied, 1638 pool); 1639 else 1640 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added, 1641 pool); 1642 1643 if (item->kind == svn_node_file) 1644 { 1645 const svn_string_t *propval; 1646 1647 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath, 1648 SVN_PROP_MIME_TYPE, pool, pool)); 1649 1650 if (propval) 1651 notify->mime_type = propval->data; 1652 } 1653 } 1654 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1655 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)) 1656 { 1657 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified, 1658 pool); 1659 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1660 notify->content_state = svn_wc_notify_state_changed; 1661 else 1662 notify->content_state = svn_wc_notify_state_unchanged; 1663 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1664 notify->prop_state = svn_wc_notify_state_changed; 1665 else 1666 notify->prop_state = svn_wc_notify_state_unchanged; 1667 } 1668 else 1669 notify = NULL; 1670 1671 1672 if (notify) 1673 { 1674 notify->kind = item->kind; 1675 notify->path_prefix = icb->notify_path_prefix; 1676 ctx->notify_func2(ctx->notify_baton2, notify, pool); 1677 } 1678 } 1679 1680 /* If this item is supposed to be deleted, do so. */ 1681 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1682 { 1683 SVN_ERR_ASSERT(parent_baton); 1684 err = editor->delete_entry(path, item->revision, 1685 parent_baton, pool); 1686 1687 if (err) 1688 goto fixup_error; 1689 } 1690 1691 /* If this item is supposed to be added, do so. */ 1692 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1693 { 1694 if (kind == svn_node_file) 1695 { 1696 SVN_ERR_ASSERT(parent_baton); 1697 err = editor->add_file( 1698 path, parent_baton, item->copyfrom_url, 1699 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1700 file_pool, &file_baton); 1701 } 1702 else /* May be svn_node_none when adding parent dirs for a copy. */ 1703 { 1704 SVN_ERR_ASSERT(parent_baton); 1705 err = editor->add_directory( 1706 path, parent_baton, item->copyfrom_url, 1707 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1708 pool, dir_baton); 1709 } 1710 1711 if (err) 1712 goto fixup_error; 1713 1714 /* Set other prop-changes, if available in the baton */ 1715 if (item->outgoing_prop_changes) 1716 { 1717 svn_prop_t *prop; 1718 apr_array_header_t *prop_changes = item->outgoing_prop_changes; 1719 int ctr; 1720 for (ctr = 0; ctr < prop_changes->nelts; ctr++) 1721 { 1722 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *); 1723 if (kind == svn_node_file) 1724 { 1725 err = editor->change_file_prop(file_baton, prop->name, 1726 prop->value, pool); 1727 } 1728 else 1729 { 1730 err = editor->change_dir_prop(*dir_baton, prop->name, 1731 prop->value, pool); 1732 } 1733 1734 if (err) 1735 goto fixup_error; 1736 } 1737 } 1738 } 1739 1740 /* Now handle property mods. */ 1741 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1742 { 1743 if (kind == svn_node_file) 1744 { 1745 if (! file_baton) 1746 { 1747 SVN_ERR_ASSERT(parent_baton); 1748 err = editor->open_file(path, parent_baton, 1749 item->revision, 1750 file_pool, &file_baton); 1751 1752 if (err) 1753 goto fixup_error; 1754 } 1755 } 1756 else 1757 { 1758 if (! *dir_baton) 1759 { 1760 if (! parent_baton) 1761 { 1762 err = editor->open_root(edit_baton, item->revision, 1763 pool, dir_baton); 1764 } 1765 else 1766 { 1767 err = editor->open_directory(path, parent_baton, 1768 item->revision, 1769 pool, dir_baton); 1770 } 1771 1772 if (err) 1773 goto fixup_error; 1774 } 1775 } 1776 1777 /* When committing a directory that no longer exists in the 1778 repository, a "not found" error does not occur immediately 1779 upon opening the directory. It appears here during the delta 1780 transmisssion. */ 1781 err = svn_wc_transmit_prop_deltas2( 1782 ctx->wc_ctx, local_abspath, editor, 1783 (kind == svn_node_dir) ? *dir_baton : file_baton, pool); 1784 1785 if (err) 1786 goto fixup_error; 1787 1788 /* Make any additional client -> repository prop changes. */ 1789 if (item->outgoing_prop_changes) 1790 { 1791 svn_prop_t *prop; 1792 int i; 1793 1794 for (i = 0; i < item->outgoing_prop_changes->nelts; i++) 1795 { 1796 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i, 1797 svn_prop_t *); 1798 if (kind == svn_node_file) 1799 { 1800 err = editor->change_file_prop(file_baton, prop->name, 1801 prop->value, pool); 1802 } 1803 else 1804 { 1805 err = editor->change_dir_prop(*dir_baton, prop->name, 1806 prop->value, pool); 1807 } 1808 1809 if (err) 1810 goto fixup_error; 1811 } 1812 } 1813 } 1814 1815 /* Finally, handle text mods (in that we need to open a file if it 1816 hasn't already been opened, and we need to put the file baton in 1817 our FILES hash). */ 1818 if ((kind == svn_node_file) 1819 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1820 { 1821 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod)); 1822 1823 if (! file_baton) 1824 { 1825 SVN_ERR_ASSERT(parent_baton); 1826 err = editor->open_file(path, parent_baton, 1827 item->revision, 1828 file_pool, &file_baton); 1829 1830 if (err) 1831 goto fixup_error; 1832 } 1833 1834 /* Add this file mod to the FILE_MODS hash. */ 1835 mod->item = item; 1836 mod->file_baton = file_baton; 1837 mod->file_pool = file_pool; 1838 svn_hash_sets(file_mods, item->session_relpath, mod); 1839 } 1840 else if (file_baton) 1841 { 1842 /* Close any outstanding file batons that didn't get caught by 1843 the "has local mods" conditional above. */ 1844 err = editor->close_file(file_baton, NULL, file_pool); 1845 svn_pool_destroy(file_pool); 1846 if (err) 1847 goto fixup_error; 1848 } 1849 1850 return SVN_NO_ERROR; 1851 1852fixup_error: 1853 return svn_error_trace(fixup_commit_error(local_abspath, 1854 icb->base_url, 1855 path, kind, 1856 err, ctx, pool)); 1857} 1858 1859svn_error_t * 1860svn_client__do_commit(const char *base_url, 1861 const apr_array_header_t *commit_items, 1862 const svn_delta_editor_t *editor, 1863 void *edit_baton, 1864 const char *notify_path_prefix, 1865 apr_hash_t **sha1_checksums, 1866 svn_client_ctx_t *ctx, 1867 apr_pool_t *result_pool, 1868 apr_pool_t *scratch_pool) 1869{ 1870 apr_hash_t *file_mods = apr_hash_make(scratch_pool); 1871 apr_hash_t *items_hash = apr_hash_make(scratch_pool); 1872 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1873 apr_hash_index_t *hi; 1874 int i; 1875 struct item_commit_baton cb_baton; 1876 apr_array_header_t *paths = 1877 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *)); 1878 1879 /* Ditto for the checksums. */ 1880 if (sha1_checksums) 1881 *sha1_checksums = apr_hash_make(result_pool); 1882 1883 /* Build a hash from our COMMIT_ITEMS array, keyed on the 1884 relative paths (which come from the item URLs). And 1885 keep an array of those decoded paths, too. */ 1886 for (i = 0; i < commit_items->nelts; i++) 1887 { 1888 svn_client_commit_item3_t *item = 1889 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1890 const char *path = item->session_relpath; 1891 svn_hash_sets(items_hash, path, item); 1892 APR_ARRAY_PUSH(paths, const char *) = path; 1893 } 1894 1895 /* Setup the callback baton. */ 1896 cb_baton.file_mods = file_mods; 1897 cb_baton.notify_path_prefix = notify_path_prefix; 1898 cb_baton.ctx = ctx; 1899 cb_baton.commit_items = items_hash; 1900 cb_baton.base_url = base_url; 1901 1902 /* Drive the commit editor! */ 1903 SVN_ERR(svn_delta_path_driver3(editor, edit_baton, paths, TRUE, 1904 do_item_commit, &cb_baton, scratch_pool)); 1905 1906 /* Transmit outstanding text deltas. */ 1907 for (hi = apr_hash_first(scratch_pool, file_mods); 1908 hi; 1909 hi = apr_hash_next(hi)) 1910 { 1911 struct file_mod_t *mod = apr_hash_this_val(hi); 1912 const svn_client_commit_item3_t *item = mod->item; 1913 const svn_checksum_t *new_text_base_md5_checksum; 1914 const svn_checksum_t *new_text_base_sha1_checksum; 1915 svn_boolean_t fulltext = FALSE; 1916 svn_error_t *err; 1917 1918 svn_pool_clear(iterpool); 1919 1920 /* Transmit the entry. */ 1921 if (ctx->cancel_func) 1922 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1923 1924 if (ctx->notify_func2) 1925 { 1926 svn_wc_notify_t *notify; 1927 notify = svn_wc_create_notify(item->path, 1928 svn_wc_notify_commit_postfix_txdelta, 1929 iterpool); 1930 notify->kind = svn_node_file; 1931 notify->path_prefix = notify_path_prefix; 1932 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1933 } 1934 1935 /* If the node has no history, transmit full text */ 1936 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1937 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 1938 fulltext = TRUE; 1939 1940 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum, 1941 &new_text_base_sha1_checksum, 1942 ctx->wc_ctx, item->path, 1943 fulltext, editor, mod->file_baton, 1944 result_pool, iterpool); 1945 1946 if (err) 1947 { 1948 svn_pool_destroy(iterpool); /* Close tempfiles */ 1949 return svn_error_trace(fixup_commit_error(item->path, 1950 base_url, 1951 item->session_relpath, 1952 svn_node_file, 1953 err, ctx, scratch_pool)); 1954 } 1955 1956 if (sha1_checksums) 1957 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum); 1958 1959 svn_pool_destroy(mod->file_pool); 1960 } 1961 1962 if (ctx->notify_func2) 1963 { 1964 svn_wc_notify_t *notify; 1965 notify = svn_wc_create_notify_url(base_url, 1966 svn_wc_notify_commit_finalizing, 1967 iterpool); 1968 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1969 } 1970 1971 svn_pool_destroy(iterpool); 1972 1973 /* Close the edit. */ 1974 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool)); 1975} 1976 1977 1978svn_error_t * 1979svn_client__get_log_msg(const char **log_msg, 1980 const char **tmp_file, 1981 const apr_array_header_t *commit_items, 1982 svn_client_ctx_t *ctx, 1983 apr_pool_t *pool) 1984{ 1985 if (ctx->log_msg_func3) 1986 { 1987 /* The client provided a callback function for the current API. 1988 Forward the call to it directly. */ 1989 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items, 1990 ctx->log_msg_baton3, pool); 1991 } 1992 else if (ctx->log_msg_func2 || ctx->log_msg_func) 1993 { 1994 /* The client provided a pre-1.5 (or pre-1.3) API callback 1995 function. Convert the commit_items list to the appropriate 1996 type, and forward call to it. */ 1997 svn_error_t *err; 1998 apr_pool_t *scratch_pool = svn_pool_create(pool); 1999 apr_array_header_t *old_commit_items = 2000 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*)); 2001 2002 int i; 2003 for (i = 0; i < commit_items->nelts; i++) 2004 { 2005 svn_client_commit_item3_t *item = 2006 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 2007 2008 if (ctx->log_msg_func2) 2009 { 2010 svn_client_commit_item2_t *old_item = 2011 apr_pcalloc(scratch_pool, sizeof(*old_item)); 2012 2013 old_item->path = item->path; 2014 old_item->kind = item->kind; 2015 old_item->url = item->url; 2016 old_item->revision = item->revision; 2017 old_item->copyfrom_url = item->copyfrom_url; 2018 old_item->copyfrom_rev = item->copyfrom_rev; 2019 old_item->state_flags = item->state_flags; 2020 old_item->wcprop_changes = item->incoming_prop_changes; 2021 2022 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) = 2023 old_item; 2024 } 2025 else /* ctx->log_msg_func */ 2026 { 2027 svn_client_commit_item_t *old_item = 2028 apr_pcalloc(scratch_pool, sizeof(*old_item)); 2029 2030 old_item->path = item->path; 2031 old_item->kind = item->kind; 2032 old_item->url = item->url; 2033 /* The pre-1.3 API used the revision field for copyfrom_rev 2034 and revision depeding of copyfrom_url. */ 2035 old_item->revision = item->copyfrom_url ? 2036 item->copyfrom_rev : item->revision; 2037 old_item->copyfrom_url = item->copyfrom_url; 2038 old_item->state_flags = item->state_flags; 2039 old_item->wcprop_changes = item->incoming_prop_changes; 2040 2041 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) = 2042 old_item; 2043 } 2044 } 2045 2046 if (ctx->log_msg_func2) 2047 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items, 2048 ctx->log_msg_baton2, pool); 2049 else 2050 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items, 2051 ctx->log_msg_baton, pool); 2052 svn_pool_destroy(scratch_pool); 2053 return err; 2054 } 2055 else 2056 { 2057 /* No log message callback was provided by the client. */ 2058 *log_msg = ""; 2059 *tmp_file = NULL; 2060 return SVN_NO_ERROR; 2061 } 2062} 2063 2064svn_error_t * 2065svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out, 2066 const apr_hash_t *revprop_table_in, 2067 const char *log_msg, 2068 svn_client_ctx_t *ctx, 2069 apr_pool_t *pool) 2070{ 2071 apr_hash_t *new_revprop_table; 2072 if (revprop_table_in) 2073 { 2074 if (svn_prop_has_svn_prop(revprop_table_in, pool)) 2075 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 2076 _("Standard properties can't be set " 2077 "explicitly as revision properties")); 2078 new_revprop_table = apr_hash_copy(pool, revprop_table_in); 2079 } 2080 else 2081 { 2082 new_revprop_table = apr_hash_make(pool); 2083 } 2084 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG, 2085 svn_string_create(log_msg, pool)); 2086 *revprop_table_out = new_revprop_table; 2087 return SVN_NO_ERROR; 2088} 2089