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