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 1395 1396svn_error_t * 1397svn_client__condense_commit_items(const char **base_url, 1398 apr_array_header_t *commit_items, 1399 apr_pool_t *pool) 1400{ 1401 apr_array_header_t *ci = commit_items; /* convenience */ 1402 const char *url; 1403 svn_client_commit_item3_t *item, *last_item = NULL; 1404 int i; 1405 1406 SVN_ERR_ASSERT(ci && ci->nelts); 1407 1408 /* Sort our commit items by their URLs. */ 1409 svn_sort__array(ci, sort_commit_item_urls); 1410 1411 /* Loop through the URLs, finding the longest usable ancestor common 1412 to all of them, and making sure there are no duplicate URLs. */ 1413 for (i = 0; i < ci->nelts; i++) 1414 { 1415 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1416 url = item->url; 1417 1418 if ((last_item) && (strcmp(last_item->url, url) == 0)) 1419 return svn_error_createf 1420 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL, 1421 _("Cannot commit both '%s' and '%s' as they refer to the same URL"), 1422 svn_dirent_local_style(item->path, pool), 1423 svn_dirent_local_style(last_item->path, pool)); 1424 1425 /* In the first iteration, our BASE_URL is just our only 1426 encountered commit URL to date. After that, we find the 1427 longest ancestor between the current BASE_URL and the current 1428 commit URL. */ 1429 if (i == 0) 1430 *base_url = apr_pstrdup(pool, url); 1431 else 1432 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool); 1433 1434 /* If our BASE_URL is itself a to-be-committed item, and it is 1435 anything other than an already-versioned directory with 1436 property mods, we'll call its parent directory URL the 1437 BASE_URL. Why? Because we can't have a file URL as our base 1438 -- period -- and all other directory operations (removal, 1439 addition, etc.) require that we open that directory's parent 1440 dir first. */ 1441 /* ### I don't understand the strlen()s here, hmmm. -kff */ 1442 if ((strlen(*base_url) == strlen(url)) 1443 && (! ((item->kind == svn_node_dir) 1444 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS))) 1445 *base_url = svn_uri_dirname(*base_url, pool); 1446 1447 /* Stash our item here for the next iteration. */ 1448 last_item = item; 1449 } 1450 1451 /* Now that we've settled on a *BASE_URL, go hack that base off 1452 of all of our URLs and store it as session_relpath. */ 1453 for (i = 0; i < ci->nelts; i++) 1454 { 1455 svn_client_commit_item3_t *this_item 1456 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1457 1458 this_item->session_relpath = svn_uri_skip_ancestor(*base_url, 1459 this_item->url, pool); 1460 } 1461#ifdef SVN_CLIENT_COMMIT_DEBUG 1462 /* ### TEMPORARY CODE ### */ 1463 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url)); 1464 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n")); 1465 for (i = 0; i < ci->nelts; i++) 1466 { 1467 svn_client_commit_item3_t *this_item 1468 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1469 char flags[6]; 1470 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1471 ? 'a' : '-'; 1472 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1473 ? 'd' : '-'; 1474 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1475 ? 't' : '-'; 1476 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1477 ? 'p' : '-'; 1478 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1479 ? 'c' : '-'; 1480 flags[5] = '\0'; 1481 SVN_DBG((" %s %6ld '%s' (%s)\n", 1482 flags, 1483 this_item->revision, 1484 this_item->url ? this_item->url : "", 1485 this_item->copyfrom_url ? this_item->copyfrom_url : "none")); 1486 } 1487#endif /* SVN_CLIENT_COMMIT_DEBUG */ 1488 1489 return SVN_NO_ERROR; 1490} 1491 1492 1493struct file_mod_t 1494{ 1495 const svn_client_commit_item3_t *item; 1496 void *file_baton; 1497 apr_pool_t *file_pool; 1498}; 1499 1500 1501/* A baton for use while driving a path-based editor driver for commit */ 1502struct item_commit_baton 1503{ 1504 const svn_delta_editor_t *editor; /* commit editor */ 1505 void *edit_baton; /* commit editor's baton */ 1506 apr_hash_t *file_mods; /* hash: path->file_mod_t */ 1507 const char *notify_path_prefix; /* notification path prefix 1508 (NULL is okay, else abs path) */ 1509 svn_client_ctx_t *ctx; /* client context baton */ 1510 apr_hash_t *commit_items; /* the committables */ 1511 const char *base_url; /* The session url for the commit */ 1512}; 1513 1514 1515/* Drive CALLBACK_BATON->editor with the change described by the item in 1516 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change 1517 * includes a text mod, however, call the editor's file_open() function 1518 * but do not send the text mod to the editor; instead, add a mapping of 1519 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods. 1520 * 1521 * Before driving the editor, call the cancellation and notification 1522 * callbacks in CALLBACK_BATON->ctx, if present. 1523 * 1524 * This implements svn_delta_path_driver_cb_func_t. */ 1525static svn_error_t * 1526do_item_commit(void **dir_baton, 1527 void *parent_baton, 1528 void *callback_baton, 1529 const char *path, 1530 apr_pool_t *pool) 1531{ 1532 struct item_commit_baton *icb = callback_baton; 1533 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items, 1534 path); 1535 svn_node_kind_t kind = item->kind; 1536 void *file_baton = NULL; 1537 apr_pool_t *file_pool = NULL; 1538 const svn_delta_editor_t *editor = icb->editor; 1539 apr_hash_t *file_mods = icb->file_mods; 1540 svn_client_ctx_t *ctx = icb->ctx; 1541 svn_error_t *err; 1542 const char *local_abspath = NULL; 1543 1544 /* Do some initializations. */ 1545 *dir_baton = NULL; 1546 if (item->kind != svn_node_none && item->path) 1547 { 1548 /* We always get an absolute path, see svn_client_commit_item3_t. */ 1549 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); 1550 local_abspath = item->path; 1551 } 1552 1553 /* If this is a file with textual mods, we'll be keeping its baton 1554 around until the end of the commit. So just lump its memory into 1555 a single, big, all-the-file-batons-in-here pool. Otherwise, we 1556 can just use POOL, and trust our caller to clean that mess up. */ 1557 if ((kind == svn_node_file) 1558 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1559 file_pool = apr_hash_pool_get(file_mods); 1560 else 1561 file_pool = pool; 1562 1563 /* Subpools are cheap, but memory isn't */ 1564 file_pool = svn_pool_create(file_pool); 1565 1566 /* Call the cancellation function. */ 1567 if (ctx->cancel_func) 1568 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1569 1570 /* Validation. */ 1571 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1572 { 1573 if (! item->copyfrom_url) 1574 return svn_error_createf 1575 (SVN_ERR_BAD_URL, NULL, 1576 _("Commit item '%s' has copy flag but no copyfrom URL"), 1577 svn_dirent_local_style(path, pool)); 1578 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev)) 1579 return svn_error_createf 1580 (SVN_ERR_CLIENT_BAD_REVISION, NULL, 1581 _("Commit item '%s' has copy flag but an invalid revision"), 1582 svn_dirent_local_style(path, pool)); 1583 } 1584 1585 /* If a feedback table was supplied by the application layer, 1586 describe what we're about to do to this item. */ 1587 if (ctx->notify_func2 && item->path) 1588 { 1589 const char *npath = item->path; 1590 svn_wc_notify_t *notify; 1591 1592 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1593 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1594 { 1595 /* We don't print the "(bin)" notice for binary files when 1596 replacing, only when adding. So we don't bother to get 1597 the mime-type here. */ 1598 if (item->copyfrom_url) 1599 notify = svn_wc_create_notify(npath, 1600 svn_wc_notify_commit_copied_replaced, 1601 pool); 1602 else 1603 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced, 1604 pool); 1605 1606 } 1607 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1608 { 1609 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted, 1610 pool); 1611 } 1612 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1613 { 1614 if (item->copyfrom_url) 1615 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied, 1616 pool); 1617 else 1618 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added, 1619 pool); 1620 1621 if (item->kind == svn_node_file) 1622 { 1623 const svn_string_t *propval; 1624 1625 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath, 1626 SVN_PROP_MIME_TYPE, pool, pool)); 1627 1628 if (propval) 1629 notify->mime_type = propval->data; 1630 } 1631 } 1632 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1633 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)) 1634 { 1635 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified, 1636 pool); 1637 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1638 notify->content_state = svn_wc_notify_state_changed; 1639 else 1640 notify->content_state = svn_wc_notify_state_unchanged; 1641 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1642 notify->prop_state = svn_wc_notify_state_changed; 1643 else 1644 notify->prop_state = svn_wc_notify_state_unchanged; 1645 } 1646 else 1647 notify = NULL; 1648 1649 1650 if (notify) 1651 { 1652 notify->kind = item->kind; 1653 notify->path_prefix = icb->notify_path_prefix; 1654 ctx->notify_func2(ctx->notify_baton2, notify, pool); 1655 } 1656 } 1657 1658 /* If this item is supposed to be deleted, do so. */ 1659 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1660 { 1661 SVN_ERR_ASSERT(parent_baton); 1662 err = editor->delete_entry(path, item->revision, 1663 parent_baton, pool); 1664 1665 if (err) 1666 goto fixup_error; 1667 } 1668 1669 /* If this item is supposed to be added, do so. */ 1670 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1671 { 1672 if (kind == svn_node_file) 1673 { 1674 SVN_ERR_ASSERT(parent_baton); 1675 err = editor->add_file( 1676 path, parent_baton, item->copyfrom_url, 1677 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1678 file_pool, &file_baton); 1679 } 1680 else /* May be svn_node_none when adding parent dirs for a copy. */ 1681 { 1682 SVN_ERR_ASSERT(parent_baton); 1683 err = editor->add_directory( 1684 path, parent_baton, item->copyfrom_url, 1685 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1686 pool, dir_baton); 1687 } 1688 1689 if (err) 1690 goto fixup_error; 1691 1692 /* Set other prop-changes, if available in the baton */ 1693 if (item->outgoing_prop_changes) 1694 { 1695 svn_prop_t *prop; 1696 apr_array_header_t *prop_changes = item->outgoing_prop_changes; 1697 int ctr; 1698 for (ctr = 0; ctr < prop_changes->nelts; ctr++) 1699 { 1700 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *); 1701 if (kind == svn_node_file) 1702 { 1703 err = editor->change_file_prop(file_baton, prop->name, 1704 prop->value, pool); 1705 } 1706 else 1707 { 1708 err = editor->change_dir_prop(*dir_baton, prop->name, 1709 prop->value, pool); 1710 } 1711 1712 if (err) 1713 goto fixup_error; 1714 } 1715 } 1716 } 1717 1718 /* Now handle property mods. */ 1719 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1720 { 1721 if (kind == svn_node_file) 1722 { 1723 if (! file_baton) 1724 { 1725 SVN_ERR_ASSERT(parent_baton); 1726 err = editor->open_file(path, parent_baton, 1727 item->revision, 1728 file_pool, &file_baton); 1729 1730 if (err) 1731 goto fixup_error; 1732 } 1733 } 1734 else 1735 { 1736 if (! *dir_baton) 1737 { 1738 if (! parent_baton) 1739 { 1740 err = editor->open_root(icb->edit_baton, item->revision, 1741 pool, dir_baton); 1742 } 1743 else 1744 { 1745 err = editor->open_directory(path, parent_baton, 1746 item->revision, 1747 pool, dir_baton); 1748 } 1749 1750 if (err) 1751 goto fixup_error; 1752 } 1753 } 1754 1755 /* When committing a directory that no longer exists in the 1756 repository, a "not found" error does not occur immediately 1757 upon opening the directory. It appears here during the delta 1758 transmisssion. */ 1759 err = svn_wc_transmit_prop_deltas2( 1760 ctx->wc_ctx, local_abspath, editor, 1761 (kind == svn_node_dir) ? *dir_baton : file_baton, pool); 1762 1763 if (err) 1764 goto fixup_error; 1765 1766 /* Make any additional client -> repository prop changes. */ 1767 if (item->outgoing_prop_changes) 1768 { 1769 svn_prop_t *prop; 1770 int i; 1771 1772 for (i = 0; i < item->outgoing_prop_changes->nelts; i++) 1773 { 1774 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i, 1775 svn_prop_t *); 1776 if (kind == svn_node_file) 1777 { 1778 err = editor->change_file_prop(file_baton, prop->name, 1779 prop->value, pool); 1780 } 1781 else 1782 { 1783 err = editor->change_dir_prop(*dir_baton, prop->name, 1784 prop->value, pool); 1785 } 1786 1787 if (err) 1788 goto fixup_error; 1789 } 1790 } 1791 } 1792 1793 /* Finally, handle text mods (in that we need to open a file if it 1794 hasn't already been opened, and we need to put the file baton in 1795 our FILES hash). */ 1796 if ((kind == svn_node_file) 1797 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1798 { 1799 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod)); 1800 1801 if (! file_baton) 1802 { 1803 SVN_ERR_ASSERT(parent_baton); 1804 err = editor->open_file(path, parent_baton, 1805 item->revision, 1806 file_pool, &file_baton); 1807 1808 if (err) 1809 goto fixup_error; 1810 } 1811 1812 /* Add this file mod to the FILE_MODS hash. */ 1813 mod->item = item; 1814 mod->file_baton = file_baton; 1815 mod->file_pool = file_pool; 1816 svn_hash_sets(file_mods, item->session_relpath, mod); 1817 } 1818 else if (file_baton) 1819 { 1820 /* Close any outstanding file batons that didn't get caught by 1821 the "has local mods" conditional above. */ 1822 err = editor->close_file(file_baton, NULL, file_pool); 1823 svn_pool_destroy(file_pool); 1824 if (err) 1825 goto fixup_error; 1826 } 1827 1828 return SVN_NO_ERROR; 1829 1830fixup_error: 1831 return svn_error_trace(fixup_commit_error(local_abspath, 1832 icb->base_url, 1833 path, kind, 1834 err, ctx, pool)); 1835} 1836 1837svn_error_t * 1838svn_client__do_commit(const char *base_url, 1839 const apr_array_header_t *commit_items, 1840 const svn_delta_editor_t *editor, 1841 void *edit_baton, 1842 const char *notify_path_prefix, 1843 apr_hash_t **sha1_checksums, 1844 svn_client_ctx_t *ctx, 1845 apr_pool_t *result_pool, 1846 apr_pool_t *scratch_pool) 1847{ 1848 apr_hash_t *file_mods = apr_hash_make(scratch_pool); 1849 apr_hash_t *items_hash = apr_hash_make(scratch_pool); 1850 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1851 apr_hash_index_t *hi; 1852 int i; 1853 struct item_commit_baton cb_baton; 1854 apr_array_header_t *paths = 1855 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *)); 1856 1857 /* Ditto for the checksums. */ 1858 if (sha1_checksums) 1859 *sha1_checksums = apr_hash_make(result_pool); 1860 1861 /* Build a hash from our COMMIT_ITEMS array, keyed on the 1862 relative paths (which come from the item URLs). And 1863 keep an array of those decoded paths, too. */ 1864 for (i = 0; i < commit_items->nelts; i++) 1865 { 1866 svn_client_commit_item3_t *item = 1867 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1868 const char *path = item->session_relpath; 1869 svn_hash_sets(items_hash, path, item); 1870 APR_ARRAY_PUSH(paths, const char *) = path; 1871 } 1872 1873 /* Setup the callback baton. */ 1874 cb_baton.editor = editor; 1875 cb_baton.edit_baton = edit_baton; 1876 cb_baton.file_mods = file_mods; 1877 cb_baton.notify_path_prefix = notify_path_prefix; 1878 cb_baton.ctx = ctx; 1879 cb_baton.commit_items = items_hash; 1880 cb_baton.base_url = base_url; 1881 1882 /* Drive the commit editor! */ 1883 SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1884 do_item_commit, &cb_baton, scratch_pool)); 1885 1886 /* Transmit outstanding text deltas. */ 1887 for (hi = apr_hash_first(scratch_pool, file_mods); 1888 hi; 1889 hi = apr_hash_next(hi)) 1890 { 1891 struct file_mod_t *mod = apr_hash_this_val(hi); 1892 const svn_client_commit_item3_t *item = mod->item; 1893 const svn_checksum_t *new_text_base_md5_checksum; 1894 const svn_checksum_t *new_text_base_sha1_checksum; 1895 svn_boolean_t fulltext = FALSE; 1896 svn_error_t *err; 1897 1898 svn_pool_clear(iterpool); 1899 1900 /* Transmit the entry. */ 1901 if (ctx->cancel_func) 1902 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1903 1904 if (ctx->notify_func2) 1905 { 1906 svn_wc_notify_t *notify; 1907 notify = svn_wc_create_notify(item->path, 1908 svn_wc_notify_commit_postfix_txdelta, 1909 iterpool); 1910 notify->kind = svn_node_file; 1911 notify->path_prefix = notify_path_prefix; 1912 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1913 } 1914 1915 /* If the node has no history, transmit full text */ 1916 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1917 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 1918 fulltext = TRUE; 1919 1920 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum, 1921 &new_text_base_sha1_checksum, 1922 ctx->wc_ctx, item->path, 1923 fulltext, editor, mod->file_baton, 1924 result_pool, iterpool); 1925 1926 if (err) 1927 { 1928 svn_pool_destroy(iterpool); /* Close tempfiles */ 1929 return svn_error_trace(fixup_commit_error(item->path, 1930 base_url, 1931 item->session_relpath, 1932 svn_node_file, 1933 err, ctx, scratch_pool)); 1934 } 1935 1936 if (sha1_checksums) 1937 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum); 1938 1939 svn_pool_destroy(mod->file_pool); 1940 } 1941 1942 if (ctx->notify_func2) 1943 { 1944 svn_wc_notify_t *notify; 1945 notify = svn_wc_create_notify_url(base_url, 1946 svn_wc_notify_commit_finalizing, 1947 iterpool); 1948 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1949 } 1950 1951 svn_pool_destroy(iterpool); 1952 1953 /* Close the edit. */ 1954 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool)); 1955} 1956 1957 1958svn_error_t * 1959svn_client__get_log_msg(const char **log_msg, 1960 const char **tmp_file, 1961 const apr_array_header_t *commit_items, 1962 svn_client_ctx_t *ctx, 1963 apr_pool_t *pool) 1964{ 1965 if (ctx->log_msg_func3) 1966 { 1967 /* The client provided a callback function for the current API. 1968 Forward the call to it directly. */ 1969 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items, 1970 ctx->log_msg_baton3, pool); 1971 } 1972 else if (ctx->log_msg_func2 || ctx->log_msg_func) 1973 { 1974 /* The client provided a pre-1.5 (or pre-1.3) API callback 1975 function. Convert the commit_items list to the appropriate 1976 type, and forward call to it. */ 1977 svn_error_t *err; 1978 apr_pool_t *scratch_pool = svn_pool_create(pool); 1979 apr_array_header_t *old_commit_items = 1980 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*)); 1981 1982 int i; 1983 for (i = 0; i < commit_items->nelts; i++) 1984 { 1985 svn_client_commit_item3_t *item = 1986 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1987 1988 if (ctx->log_msg_func2) 1989 { 1990 svn_client_commit_item2_t *old_item = 1991 apr_pcalloc(scratch_pool, sizeof(*old_item)); 1992 1993 old_item->path = item->path; 1994 old_item->kind = item->kind; 1995 old_item->url = item->url; 1996 old_item->revision = item->revision; 1997 old_item->copyfrom_url = item->copyfrom_url; 1998 old_item->copyfrom_rev = item->copyfrom_rev; 1999 old_item->state_flags = item->state_flags; 2000 old_item->wcprop_changes = item->incoming_prop_changes; 2001 2002 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) = 2003 old_item; 2004 } 2005 else /* ctx->log_msg_func */ 2006 { 2007 svn_client_commit_item_t *old_item = 2008 apr_pcalloc(scratch_pool, sizeof(*old_item)); 2009 2010 old_item->path = item->path; 2011 old_item->kind = item->kind; 2012 old_item->url = item->url; 2013 /* The pre-1.3 API used the revision field for copyfrom_rev 2014 and revision depeding of copyfrom_url. */ 2015 old_item->revision = item->copyfrom_url ? 2016 item->copyfrom_rev : item->revision; 2017 old_item->copyfrom_url = item->copyfrom_url; 2018 old_item->state_flags = item->state_flags; 2019 old_item->wcprop_changes = item->incoming_prop_changes; 2020 2021 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) = 2022 old_item; 2023 } 2024 } 2025 2026 if (ctx->log_msg_func2) 2027 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items, 2028 ctx->log_msg_baton2, pool); 2029 else 2030 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items, 2031 ctx->log_msg_baton, pool); 2032 svn_pool_destroy(scratch_pool); 2033 return err; 2034 } 2035 else 2036 { 2037 /* No log message callback was provided by the client. */ 2038 *log_msg = ""; 2039 *tmp_file = NULL; 2040 return SVN_NO_ERROR; 2041 } 2042} 2043 2044svn_error_t * 2045svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out, 2046 const apr_hash_t *revprop_table_in, 2047 const char *log_msg, 2048 svn_client_ctx_t *ctx, 2049 apr_pool_t *pool) 2050{ 2051 apr_hash_t *new_revprop_table; 2052 if (revprop_table_in) 2053 { 2054 if (svn_prop_has_svn_prop(revprop_table_in, pool)) 2055 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 2056 _("Standard properties can't be set " 2057 "explicitly as revision properties")); 2058 new_revprop_table = apr_hash_copy(pool, revprop_table_in); 2059 } 2060 else 2061 { 2062 new_revprop_table = apr_hash_make(pool); 2063 } 2064 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG, 2065 svn_string_create(log_msg, pool)); 2066 *revprop_table_out = new_revprop_table; 2067 return SVN_NO_ERROR; 2068} 2069