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 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}; 970 971/* Helper for the commit harvesters */ 972static svn_error_t * 973handle_descendants(void *baton, 974 const void *key, apr_ssize_t klen, void *val, 975 apr_pool_t *pool) 976{ 977 struct handle_descendants_baton *hdb = baton; 978 apr_array_header_t *commit_items = val; 979 apr_pool_t *iterpool = svn_pool_create(pool); 980 int i; 981 982 for (i = 0; i < commit_items->nelts; i++) 983 { 984 svn_client_commit_item3_t *item = 985 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 986 const apr_array_header_t *absent_descendants; 987 int j; 988 989 /* Is this a copy operation? */ 990 if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 991 || ! item->copyfrom_url) 992 continue; 993 994 if (hdb->cancel_func) 995 SVN_ERR(hdb->cancel_func(hdb->cancel_baton)); 996 997 svn_pool_clear(iterpool); 998 999 SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants, 1000 hdb->wc_ctx, item->path, 1001 iterpool, iterpool)); 1002 1003 for (j = 0; j < absent_descendants->nelts; j++) 1004 { 1005 int k; 1006 svn_boolean_t found_item = FALSE; 1007 svn_node_kind_t kind; 1008 const char *relpath = APR_ARRAY_IDX(absent_descendants, j, 1009 const char *); 1010 const char *local_abspath = svn_dirent_join(item->path, relpath, 1011 iterpool); 1012 1013 /* If the path has a commit operation, we do nothing. 1014 (It will be deleted by the operation) */ 1015 for (k = 0; k < commit_items->nelts; k++) 1016 { 1017 svn_client_commit_item3_t *cmt_item = 1018 APR_ARRAY_IDX(commit_items, k, svn_client_commit_item3_t *); 1019 1020 if (! strcmp(cmt_item->path, local_abspath)) 1021 { 1022 found_item = TRUE; 1023 break; 1024 } 1025 } 1026 1027 if (found_item) 1028 continue; /* We have an explicit delete or replace for this path */ 1029 1030 /* ### Need a sub-iterpool? */ 1031 1032 if (hdb->check_url_func) 1033 { 1034 const char *from_url = svn_path_url_add_component2( 1035 item->copyfrom_url, relpath, 1036 iterpool); 1037 1038 SVN_ERR(hdb->check_url_func(hdb->check_url_baton, 1039 &kind, from_url, item->copyfrom_rev, 1040 iterpool)); 1041 1042 if (kind == svn_node_none) 1043 continue; /* This node is already deleted */ 1044 } 1045 else 1046 kind = svn_node_unknown; /* 'Ok' for a delete of something */ 1047 1048 { 1049 /* Add a new commit item that describes the delete */ 1050 apr_pool_t *result_pool = commit_items->pool; 1051 svn_client_commit_item3_t *new_item 1052 = svn_client_commit_item3_create(result_pool); 1053 1054 new_item->path = svn_dirent_join(item->path, relpath, 1055 result_pool); 1056 new_item->kind = kind; 1057 new_item->url = svn_path_url_add_component2(item->url, relpath, 1058 result_pool); 1059 new_item->revision = SVN_INVALID_REVNUM; 1060 new_item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1061 new_item->incoming_prop_changes = apr_array_make(result_pool, 1, 1062 sizeof(svn_prop_t *)); 1063 1064 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) 1065 = new_item; 1066 } 1067 } 1068 } 1069 1070 svn_pool_destroy(iterpool); 1071 return SVN_NO_ERROR; 1072} 1073 1074/* Allocate and initialize the COMMITTABLES structure from POOL. 1075 */ 1076static void 1077create_committables(svn_client__committables_t **committables, 1078 apr_pool_t *pool) 1079{ 1080 *committables = apr_palloc(pool, sizeof(**committables)); 1081 1082 (*committables)->by_repository = apr_hash_make(pool); 1083 (*committables)->by_path = apr_hash_make(pool); 1084} 1085 1086svn_error_t * 1087svn_client__harvest_committables(svn_client__committables_t **committables, 1088 apr_hash_t **lock_tokens, 1089 const char *base_dir_abspath, 1090 const apr_array_header_t *targets, 1091 int depth_empty_start, 1092 svn_depth_t depth, 1093 svn_boolean_t just_locked, 1094 const apr_array_header_t *changelists, 1095 svn_client__check_url_kind_t check_url_func, 1096 void *check_url_baton, 1097 svn_client_ctx_t *ctx, 1098 apr_pool_t *result_pool, 1099 apr_pool_t *scratch_pool) 1100{ 1101 int i; 1102 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1103 apr_hash_t *changelist_hash = NULL; 1104 struct handle_descendants_baton hdb; 1105 apr_hash_index_t *hi; 1106 1107 /* It's possible that one of the named targets has a parent that is 1108 * itself scheduled for addition or replacement -- that is, the 1109 * parent is not yet versioned in the repository. This is okay, as 1110 * long as the parent itself is part of this same commit, either 1111 * directly, or by virtue of a grandparent, great-grandparent, etc, 1112 * being part of the commit. 1113 * 1114 * Since we don't know what's included in the commit until we've 1115 * harvested all the targets, we can't reliably check this as we 1116 * go. So in `danglers', we record named targets whose parents 1117 * do not yet exist in the repository. Then after harvesting the total 1118 * commit group, we check to make sure those parents are included. 1119 * 1120 * Each key of danglers is a parent which does not exist in the 1121 * repository. The (const char *) value is one of that parent's 1122 * children which is named as part of the commit; the child is 1123 * included only to make a better error message. 1124 * 1125 * (The reason we don't bother to check unnamed -- i.e, implicit -- 1126 * targets is that they can only join the commit if their parents 1127 * did too, so this situation can't arise for them.) 1128 */ 1129 apr_hash_t *danglers = apr_hash_make(scratch_pool); 1130 1131 SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath)); 1132 1133 /* Create the COMMITTABLES structure. */ 1134 create_committables(committables, result_pool); 1135 1136 /* And the LOCK_TOKENS dito. */ 1137 *lock_tokens = apr_hash_make(result_pool); 1138 1139 /* If we have a list of changelists, convert that into a hash with 1140 changelist keys. */ 1141 if (changelists && changelists->nelts) 1142 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, 1143 scratch_pool)); 1144 1145 for (i = 0; i < targets->nelts; ++i) 1146 { 1147 const char *target_abspath; 1148 1149 svn_pool_clear(iterpool); 1150 1151 /* Add the relative portion to the base abspath. */ 1152 target_abspath = svn_dirent_join(base_dir_abspath, 1153 APR_ARRAY_IDX(targets, i, const char *), 1154 iterpool); 1155 1156 /* Handle our TARGET. */ 1157 /* Make sure this isn't inside a working copy subtree that is 1158 * marked as tree-conflicted. */ 1159 SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath, 1160 ctx->notify_func2, 1161 ctx->notify_baton2, 1162 iterpool)); 1163 1164 /* Are the remaining items externals with depth empty? */ 1165 if (i == depth_empty_start) 1166 depth = svn_depth_empty; 1167 1168 SVN_ERR(harvest_committables(target_abspath, 1169 *committables, *lock_tokens, 1170 NULL /* COPY_MODE_RELPATH */, 1171 depth, just_locked, changelist_hash, 1172 danglers, 1173 check_url_func, check_url_baton, 1174 ctx->cancel_func, ctx->cancel_baton, 1175 ctx->notify_func2, ctx->notify_baton2, 1176 ctx->wc_ctx, result_pool, iterpool)); 1177 } 1178 1179 hdb.wc_ctx = ctx->wc_ctx; 1180 hdb.cancel_func = ctx->cancel_func; 1181 hdb.cancel_baton = ctx->cancel_baton; 1182 hdb.check_url_func = check_url_func; 1183 hdb.check_url_baton = check_url_baton; 1184 1185 SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository, 1186 handle_descendants, &hdb, iterpool)); 1187 1188 /* Make sure that every path in danglers is part of the commit. */ 1189 for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi)) 1190 { 1191 const char *dangling_parent = svn__apr_hash_index_key(hi); 1192 1193 svn_pool_clear(iterpool); 1194 1195 if (! look_up_committable(*committables, dangling_parent, iterpool)) 1196 { 1197 const char *dangling_child = svn__apr_hash_index_val(hi); 1198 1199 if (ctx->notify_func2 != NULL) 1200 { 1201 svn_wc_notify_t *notify; 1202 1203 notify = svn_wc_create_notify(dangling_child, 1204 svn_wc_notify_failed_no_parent, 1205 scratch_pool); 1206 1207 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1208 } 1209 1210 return svn_error_createf( 1211 SVN_ERR_ILLEGAL_TARGET, NULL, 1212 _("'%s' is not known to exist in the repository " 1213 "and is not part of the commit, " 1214 "yet its child '%s' is part of the commit"), 1215 /* Probably one or both of these is an entry, but 1216 safest to local_stylize just in case. */ 1217 svn_dirent_local_style(dangling_parent, iterpool), 1218 svn_dirent_local_style(dangling_child, iterpool)); 1219 } 1220 } 1221 1222 svn_pool_destroy(iterpool); 1223 1224 return SVN_NO_ERROR; 1225} 1226 1227struct copy_committables_baton 1228{ 1229 svn_client_ctx_t *ctx; 1230 svn_client__committables_t *committables; 1231 apr_pool_t *result_pool; 1232 svn_client__check_url_kind_t check_url_func; 1233 void *check_url_baton; 1234}; 1235 1236static svn_error_t * 1237harvest_copy_committables(void *baton, void *item, apr_pool_t *pool) 1238{ 1239 struct copy_committables_baton *btn = baton; 1240 svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item; 1241 const char *repos_root_url; 1242 const char *commit_relpath; 1243 struct handle_descendants_baton hdb; 1244 1245 /* Read the entry for this SRC. */ 1246 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 1247 1248 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL, 1249 btn->ctx->wc_ctx, 1250 pair->src_abspath_or_url, 1251 pool, pool)); 1252 1253 commit_relpath = svn_uri_skip_ancestor(repos_root_url, 1254 pair->dst_abspath_or_url, pool); 1255 1256 /* Handle this SRC. */ 1257 SVN_ERR(harvest_committables(pair->src_abspath_or_url, 1258 btn->committables, NULL, 1259 commit_relpath, 1260 svn_depth_infinity, 1261 FALSE, /* JUST_LOCKED */ 1262 NULL /* changelists */, 1263 NULL, 1264 btn->check_url_func, 1265 btn->check_url_baton, 1266 btn->ctx->cancel_func, 1267 btn->ctx->cancel_baton, 1268 btn->ctx->notify_func2, 1269 btn->ctx->notify_baton2, 1270 btn->ctx->wc_ctx, btn->result_pool, pool)); 1271 1272 hdb.wc_ctx = btn->ctx->wc_ctx; 1273 hdb.cancel_func = btn->ctx->cancel_func; 1274 hdb.cancel_baton = btn->ctx->cancel_baton; 1275 hdb.check_url_func = btn->check_url_func; 1276 hdb.check_url_baton = btn->check_url_baton; 1277 1278 SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository, 1279 handle_descendants, &hdb, pool)); 1280 1281 return SVN_NO_ERROR; 1282} 1283 1284 1285 1286svn_error_t * 1287svn_client__get_copy_committables(svn_client__committables_t **committables, 1288 const apr_array_header_t *copy_pairs, 1289 svn_client__check_url_kind_t check_url_func, 1290 void *check_url_baton, 1291 svn_client_ctx_t *ctx, 1292 apr_pool_t *result_pool, 1293 apr_pool_t *scratch_pool) 1294{ 1295 struct copy_committables_baton btn; 1296 1297 /* Create the COMMITTABLES structure. */ 1298 create_committables(committables, result_pool); 1299 1300 btn.ctx = ctx; 1301 btn.committables = *committables; 1302 btn.result_pool = result_pool; 1303 1304 btn.check_url_func = check_url_func; 1305 btn.check_url_baton = check_url_baton; 1306 1307 /* For each copy pair, harvest the committables for that pair into the 1308 committables hash. */ 1309 return svn_iter_apr_array(NULL, copy_pairs, 1310 harvest_copy_committables, &btn, scratch_pool); 1311} 1312 1313 1314int svn_client__sort_commit_item_urls(const void *a, const void *b) 1315{ 1316 const svn_client_commit_item3_t *item1 1317 = *((const svn_client_commit_item3_t * const *) a); 1318 const svn_client_commit_item3_t *item2 1319 = *((const svn_client_commit_item3_t * const *) b); 1320 return svn_path_compare_paths(item1->url, item2->url); 1321} 1322 1323 1324 1325svn_error_t * 1326svn_client__condense_commit_items(const char **base_url, 1327 apr_array_header_t *commit_items, 1328 apr_pool_t *pool) 1329{ 1330 apr_array_header_t *ci = commit_items; /* convenience */ 1331 const char *url; 1332 svn_client_commit_item3_t *item, *last_item = NULL; 1333 int i; 1334 1335 SVN_ERR_ASSERT(ci && ci->nelts); 1336 1337 /* Sort our commit items by their URLs. */ 1338 qsort(ci->elts, ci->nelts, 1339 ci->elt_size, svn_client__sort_commit_item_urls); 1340 1341 /* Loop through the URLs, finding the longest usable ancestor common 1342 to all of them, and making sure there are no duplicate URLs. */ 1343 for (i = 0; i < ci->nelts; i++) 1344 { 1345 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1346 url = item->url; 1347 1348 if ((last_item) && (strcmp(last_item->url, url) == 0)) 1349 return svn_error_createf 1350 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL, 1351 _("Cannot commit both '%s' and '%s' as they refer to the same URL"), 1352 svn_dirent_local_style(item->path, pool), 1353 svn_dirent_local_style(last_item->path, pool)); 1354 1355 /* In the first iteration, our BASE_URL is just our only 1356 encountered commit URL to date. After that, we find the 1357 longest ancestor between the current BASE_URL and the current 1358 commit URL. */ 1359 if (i == 0) 1360 *base_url = apr_pstrdup(pool, url); 1361 else 1362 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool); 1363 1364 /* If our BASE_URL is itself a to-be-committed item, and it is 1365 anything other than an already-versioned directory with 1366 property mods, we'll call its parent directory URL the 1367 BASE_URL. Why? Because we can't have a file URL as our base 1368 -- period -- and all other directory operations (removal, 1369 addition, etc.) require that we open that directory's parent 1370 dir first. */ 1371 /* ### I don't understand the strlen()s here, hmmm. -kff */ 1372 if ((strlen(*base_url) == strlen(url)) 1373 && (! ((item->kind == svn_node_dir) 1374 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS))) 1375 *base_url = svn_uri_dirname(*base_url, pool); 1376 1377 /* Stash our item here for the next iteration. */ 1378 last_item = item; 1379 } 1380 1381 /* Now that we've settled on a *BASE_URL, go hack that base off 1382 of all of our URLs and store it as session_relpath. */ 1383 for (i = 0; i < ci->nelts; i++) 1384 { 1385 svn_client_commit_item3_t *this_item 1386 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1387 1388 this_item->session_relpath = svn_uri_skip_ancestor(*base_url, 1389 this_item->url, pool); 1390 } 1391#ifdef SVN_CLIENT_COMMIT_DEBUG 1392 /* ### TEMPORARY CODE ### */ 1393 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url)); 1394 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n")); 1395 for (i = 0; i < ci->nelts; i++) 1396 { 1397 svn_client_commit_item3_t *this_item 1398 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1399 char flags[6]; 1400 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1401 ? 'a' : '-'; 1402 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1403 ? 'd' : '-'; 1404 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1405 ? 't' : '-'; 1406 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1407 ? 'p' : '-'; 1408 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1409 ? 'c' : '-'; 1410 flags[5] = '\0'; 1411 SVN_DBG((" %s %6ld '%s' (%s)\n", 1412 flags, 1413 this_item->revision, 1414 this_item->url ? this_item->url : "", 1415 this_item->copyfrom_url ? this_item->copyfrom_url : "none")); 1416 } 1417#endif /* SVN_CLIENT_COMMIT_DEBUG */ 1418 1419 return SVN_NO_ERROR; 1420} 1421 1422 1423struct file_mod_t 1424{ 1425 const svn_client_commit_item3_t *item; 1426 void *file_baton; 1427}; 1428 1429 1430/* A baton for use while driving a path-based editor driver for commit */ 1431struct item_commit_baton 1432{ 1433 const svn_delta_editor_t *editor; /* commit editor */ 1434 void *edit_baton; /* commit editor's baton */ 1435 apr_hash_t *file_mods; /* hash: path->file_mod_t */ 1436 const char *notify_path_prefix; /* notification path prefix 1437 (NULL is okay, else abs path) */ 1438 svn_client_ctx_t *ctx; /* client context baton */ 1439 apr_hash_t *commit_items; /* the committables */ 1440 const char *base_url; /* The session url for the commit */ 1441}; 1442 1443 1444/* Drive CALLBACK_BATON->editor with the change described by the item in 1445 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change 1446 * includes a text mod, however, call the editor's file_open() function 1447 * but do not send the text mod to the editor; instead, add a mapping of 1448 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods. 1449 * 1450 * Before driving the editor, call the cancellation and notification 1451 * callbacks in CALLBACK_BATON->ctx, if present. 1452 * 1453 * This implements svn_delta_path_driver_cb_func_t. */ 1454static svn_error_t * 1455do_item_commit(void **dir_baton, 1456 void *parent_baton, 1457 void *callback_baton, 1458 const char *path, 1459 apr_pool_t *pool) 1460{ 1461 struct item_commit_baton *icb = callback_baton; 1462 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items, 1463 path); 1464 svn_node_kind_t kind = item->kind; 1465 void *file_baton = NULL; 1466 apr_pool_t *file_pool = NULL; 1467 const svn_delta_editor_t *editor = icb->editor; 1468 apr_hash_t *file_mods = icb->file_mods; 1469 svn_client_ctx_t *ctx = icb->ctx; 1470 svn_error_t *err; 1471 const char *local_abspath = NULL; 1472 1473 /* Do some initializations. */ 1474 *dir_baton = NULL; 1475 if (item->kind != svn_node_none && item->path) 1476 { 1477 /* We always get an absolute path, see svn_client_commit_item3_t. */ 1478 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); 1479 local_abspath = item->path; 1480 } 1481 1482 /* If this is a file with textual mods, we'll be keeping its baton 1483 around until the end of the commit. So just lump its memory into 1484 a single, big, all-the-file-batons-in-here pool. Otherwise, we 1485 can just use POOL, and trust our caller to clean that mess up. */ 1486 if ((kind == svn_node_file) 1487 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1488 file_pool = apr_hash_pool_get(file_mods); 1489 else 1490 file_pool = pool; 1491 1492 /* Call the cancellation function. */ 1493 if (ctx->cancel_func) 1494 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1495 1496 /* Validation. */ 1497 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1498 { 1499 if (! item->copyfrom_url) 1500 return svn_error_createf 1501 (SVN_ERR_BAD_URL, NULL, 1502 _("Commit item '%s' has copy flag but no copyfrom URL"), 1503 svn_dirent_local_style(path, pool)); 1504 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev)) 1505 return svn_error_createf 1506 (SVN_ERR_CLIENT_BAD_REVISION, NULL, 1507 _("Commit item '%s' has copy flag but an invalid revision"), 1508 svn_dirent_local_style(path, pool)); 1509 } 1510 1511 /* If a feedback table was supplied by the application layer, 1512 describe what we're about to do to this item. */ 1513 if (ctx->notify_func2 && item->path) 1514 { 1515 const char *npath = item->path; 1516 svn_wc_notify_t *notify; 1517 1518 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1519 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1520 { 1521 /* We don't print the "(bin)" notice for binary files when 1522 replacing, only when adding. So we don't bother to get 1523 the mime-type here. */ 1524 if (item->copyfrom_url) 1525 notify = svn_wc_create_notify(npath, 1526 svn_wc_notify_commit_copied_replaced, 1527 pool); 1528 else 1529 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced, 1530 pool); 1531 1532 } 1533 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1534 { 1535 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted, 1536 pool); 1537 } 1538 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1539 { 1540 if (item->copyfrom_url) 1541 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied, 1542 pool); 1543 else 1544 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added, 1545 pool); 1546 1547 if (item->kind == svn_node_file) 1548 { 1549 const svn_string_t *propval; 1550 1551 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath, 1552 SVN_PROP_MIME_TYPE, pool, pool)); 1553 1554 if (propval) 1555 notify->mime_type = propval->data; 1556 } 1557 } 1558 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1559 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)) 1560 { 1561 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified, 1562 pool); 1563 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1564 notify->content_state = svn_wc_notify_state_changed; 1565 else 1566 notify->content_state = svn_wc_notify_state_unchanged; 1567 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1568 notify->prop_state = svn_wc_notify_state_changed; 1569 else 1570 notify->prop_state = svn_wc_notify_state_unchanged; 1571 } 1572 else 1573 notify = NULL; 1574 1575 if (notify) 1576 { 1577 notify->kind = item->kind; 1578 notify->path_prefix = icb->notify_path_prefix; 1579 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1580 } 1581 } 1582 1583 /* If this item is supposed to be deleted, do so. */ 1584 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1585 { 1586 SVN_ERR_ASSERT(parent_baton); 1587 err = editor->delete_entry(path, item->revision, 1588 parent_baton, pool); 1589 1590 if (err) 1591 goto fixup_error; 1592 } 1593 1594 /* If this item is supposed to be added, do so. */ 1595 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1596 { 1597 if (kind == svn_node_file) 1598 { 1599 SVN_ERR_ASSERT(parent_baton); 1600 err = editor->add_file( 1601 path, parent_baton, item->copyfrom_url, 1602 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1603 file_pool, &file_baton); 1604 } 1605 else /* May be svn_node_none when adding parent dirs for a copy. */ 1606 { 1607 SVN_ERR_ASSERT(parent_baton); 1608 err = editor->add_directory( 1609 path, parent_baton, item->copyfrom_url, 1610 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1611 pool, dir_baton); 1612 } 1613 1614 if (err) 1615 goto fixup_error; 1616 1617 /* Set other prop-changes, if available in the baton */ 1618 if (item->outgoing_prop_changes) 1619 { 1620 svn_prop_t *prop; 1621 apr_array_header_t *prop_changes = item->outgoing_prop_changes; 1622 int ctr; 1623 for (ctr = 0; ctr < prop_changes->nelts; ctr++) 1624 { 1625 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *); 1626 if (kind == svn_node_file) 1627 { 1628 err = editor->change_file_prop(file_baton, prop->name, 1629 prop->value, pool); 1630 } 1631 else 1632 { 1633 err = editor->change_dir_prop(*dir_baton, prop->name, 1634 prop->value, pool); 1635 } 1636 1637 if (err) 1638 goto fixup_error; 1639 } 1640 } 1641 } 1642 1643 /* Now handle property mods. */ 1644 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1645 { 1646 if (kind == svn_node_file) 1647 { 1648 if (! file_baton) 1649 { 1650 SVN_ERR_ASSERT(parent_baton); 1651 err = editor->open_file(path, parent_baton, 1652 item->revision, 1653 file_pool, &file_baton); 1654 1655 if (err) 1656 goto fixup_error; 1657 } 1658 } 1659 else 1660 { 1661 if (! *dir_baton) 1662 { 1663 if (! parent_baton) 1664 { 1665 err = editor->open_root(icb->edit_baton, item->revision, 1666 pool, dir_baton); 1667 } 1668 else 1669 { 1670 err = editor->open_directory(path, parent_baton, 1671 item->revision, 1672 pool, dir_baton); 1673 } 1674 1675 if (err) 1676 goto fixup_error; 1677 } 1678 } 1679 1680 /* When committing a directory that no longer exists in the 1681 repository, a "not found" error does not occur immediately 1682 upon opening the directory. It appears here during the delta 1683 transmisssion. */ 1684 err = svn_wc_transmit_prop_deltas2( 1685 ctx->wc_ctx, local_abspath, editor, 1686 (kind == svn_node_dir) ? *dir_baton : file_baton, pool); 1687 1688 if (err) 1689 goto fixup_error; 1690 1691 /* Make any additional client -> repository prop changes. */ 1692 if (item->outgoing_prop_changes) 1693 { 1694 svn_prop_t *prop; 1695 int i; 1696 1697 for (i = 0; i < item->outgoing_prop_changes->nelts; i++) 1698 { 1699 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i, 1700 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 /* Finally, handle text mods (in that we need to open a file if it 1719 hasn't already been opened, and we need to put the file baton in 1720 our FILES hash). */ 1721 if ((kind == svn_node_file) 1722 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1723 { 1724 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod)); 1725 1726 if (! file_baton) 1727 { 1728 SVN_ERR_ASSERT(parent_baton); 1729 err = editor->open_file(path, parent_baton, 1730 item->revision, 1731 file_pool, &file_baton); 1732 1733 if (err) 1734 goto fixup_error; 1735 } 1736 1737 /* Add this file mod to the FILE_MODS hash. */ 1738 mod->item = item; 1739 mod->file_baton = file_baton; 1740 svn_hash_sets(file_mods, item->session_relpath, mod); 1741 } 1742 else if (file_baton) 1743 { 1744 /* Close any outstanding file batons that didn't get caught by 1745 the "has local mods" conditional above. */ 1746 err = editor->close_file(file_baton, NULL, file_pool); 1747 1748 if (err) 1749 goto fixup_error; 1750 } 1751 1752 return SVN_NO_ERROR; 1753 1754fixup_error: 1755 return svn_error_trace(fixup_commit_error(local_abspath, 1756 icb->base_url, 1757 path, kind, 1758 err, ctx, pool)); 1759} 1760 1761svn_error_t * 1762svn_client__do_commit(const char *base_url, 1763 const apr_array_header_t *commit_items, 1764 const svn_delta_editor_t *editor, 1765 void *edit_baton, 1766 const char *notify_path_prefix, 1767 apr_hash_t **sha1_checksums, 1768 svn_client_ctx_t *ctx, 1769 apr_pool_t *result_pool, 1770 apr_pool_t *scratch_pool) 1771{ 1772 apr_hash_t *file_mods = apr_hash_make(scratch_pool); 1773 apr_hash_t *items_hash = apr_hash_make(scratch_pool); 1774 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1775 apr_hash_index_t *hi; 1776 int i; 1777 struct item_commit_baton cb_baton; 1778 apr_array_header_t *paths = 1779 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *)); 1780 1781 /* Ditto for the checksums. */ 1782 if (sha1_checksums) 1783 *sha1_checksums = apr_hash_make(result_pool); 1784 1785 /* Build a hash from our COMMIT_ITEMS array, keyed on the 1786 relative paths (which come from the item URLs). And 1787 keep an array of those decoded paths, too. */ 1788 for (i = 0; i < commit_items->nelts; i++) 1789 { 1790 svn_client_commit_item3_t *item = 1791 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1792 const char *path = item->session_relpath; 1793 svn_hash_sets(items_hash, path, item); 1794 APR_ARRAY_PUSH(paths, const char *) = path; 1795 } 1796 1797 /* Setup the callback baton. */ 1798 cb_baton.editor = editor; 1799 cb_baton.edit_baton = edit_baton; 1800 cb_baton.file_mods = file_mods; 1801 cb_baton.notify_path_prefix = notify_path_prefix; 1802 cb_baton.ctx = ctx; 1803 cb_baton.commit_items = items_hash; 1804 cb_baton.base_url = base_url; 1805 1806 /* Drive the commit editor! */ 1807 SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1808 do_item_commit, &cb_baton, scratch_pool)); 1809 1810 /* Transmit outstanding text deltas. */ 1811 for (hi = apr_hash_first(scratch_pool, file_mods); 1812 hi; 1813 hi = apr_hash_next(hi)) 1814 { 1815 struct file_mod_t *mod = svn__apr_hash_index_val(hi); 1816 const svn_client_commit_item3_t *item = mod->item; 1817 const svn_checksum_t *new_text_base_md5_checksum; 1818 const svn_checksum_t *new_text_base_sha1_checksum; 1819 svn_boolean_t fulltext = FALSE; 1820 svn_error_t *err; 1821 1822 svn_pool_clear(iterpool); 1823 1824 /* Transmit the entry. */ 1825 if (ctx->cancel_func) 1826 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1827 1828 if (ctx->notify_func2) 1829 { 1830 svn_wc_notify_t *notify; 1831 notify = svn_wc_create_notify(item->path, 1832 svn_wc_notify_commit_postfix_txdelta, 1833 iterpool); 1834 notify->kind = svn_node_file; 1835 notify->path_prefix = notify_path_prefix; 1836 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1837 } 1838 1839 /* If the node has no history, transmit full text */ 1840 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1841 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 1842 fulltext = TRUE; 1843 1844 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum, 1845 &new_text_base_sha1_checksum, 1846 ctx->wc_ctx, item->path, 1847 fulltext, editor, mod->file_baton, 1848 result_pool, iterpool); 1849 1850 if (err) 1851 { 1852 svn_pool_destroy(iterpool); /* Close tempfiles */ 1853 return svn_error_trace(fixup_commit_error(item->path, 1854 base_url, 1855 item->session_relpath, 1856 svn_node_file, 1857 err, ctx, scratch_pool)); 1858 } 1859 1860 if (sha1_checksums) 1861 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum); 1862 } 1863 1864 svn_pool_destroy(iterpool); 1865 1866 /* Close the edit. */ 1867 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool)); 1868} 1869 1870 1871svn_error_t * 1872svn_client__get_log_msg(const char **log_msg, 1873 const char **tmp_file, 1874 const apr_array_header_t *commit_items, 1875 svn_client_ctx_t *ctx, 1876 apr_pool_t *pool) 1877{ 1878 if (ctx->log_msg_func3) 1879 { 1880 /* The client provided a callback function for the current API. 1881 Forward the call to it directly. */ 1882 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items, 1883 ctx->log_msg_baton3, pool); 1884 } 1885 else if (ctx->log_msg_func2 || ctx->log_msg_func) 1886 { 1887 /* The client provided a pre-1.5 (or pre-1.3) API callback 1888 function. Convert the commit_items list to the appropriate 1889 type, and forward call to it. */ 1890 svn_error_t *err; 1891 apr_pool_t *scratch_pool = svn_pool_create(pool); 1892 apr_array_header_t *old_commit_items = 1893 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*)); 1894 1895 int i; 1896 for (i = 0; i < commit_items->nelts; i++) 1897 { 1898 svn_client_commit_item3_t *item = 1899 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1900 1901 if (ctx->log_msg_func2) 1902 { 1903 svn_client_commit_item2_t *old_item = 1904 apr_pcalloc(scratch_pool, sizeof(*old_item)); 1905 1906 old_item->path = item->path; 1907 old_item->kind = item->kind; 1908 old_item->url = item->url; 1909 old_item->revision = item->revision; 1910 old_item->copyfrom_url = item->copyfrom_url; 1911 old_item->copyfrom_rev = item->copyfrom_rev; 1912 old_item->state_flags = item->state_flags; 1913 old_item->wcprop_changes = item->incoming_prop_changes; 1914 1915 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) = 1916 old_item; 1917 } 1918 else /* ctx->log_msg_func */ 1919 { 1920 svn_client_commit_item_t *old_item = 1921 apr_pcalloc(scratch_pool, sizeof(*old_item)); 1922 1923 old_item->path = item->path; 1924 old_item->kind = item->kind; 1925 old_item->url = item->url; 1926 /* The pre-1.3 API used the revision field for copyfrom_rev 1927 and revision depeding of copyfrom_url. */ 1928 old_item->revision = item->copyfrom_url ? 1929 item->copyfrom_rev : item->revision; 1930 old_item->copyfrom_url = item->copyfrom_url; 1931 old_item->state_flags = item->state_flags; 1932 old_item->wcprop_changes = item->incoming_prop_changes; 1933 1934 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) = 1935 old_item; 1936 } 1937 } 1938 1939 if (ctx->log_msg_func2) 1940 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items, 1941 ctx->log_msg_baton2, pool); 1942 else 1943 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items, 1944 ctx->log_msg_baton, pool); 1945 svn_pool_destroy(scratch_pool); 1946 return err; 1947 } 1948 else 1949 { 1950 /* No log message callback was provided by the client. */ 1951 *log_msg = ""; 1952 *tmp_file = NULL; 1953 return SVN_NO_ERROR; 1954 } 1955} 1956 1957svn_error_t * 1958svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out, 1959 const apr_hash_t *revprop_table_in, 1960 const char *log_msg, 1961 svn_client_ctx_t *ctx, 1962 apr_pool_t *pool) 1963{ 1964 apr_hash_t *new_revprop_table; 1965 if (revprop_table_in) 1966 { 1967 if (svn_prop_has_svn_prop(revprop_table_in, pool)) 1968 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 1969 _("Standard properties can't be set " 1970 "explicitly as revision properties")); 1971 new_revprop_table = apr_hash_copy(pool, revprop_table_in); 1972 } 1973 else 1974 { 1975 new_revprop_table = apr_hash_make(pool); 1976 } 1977 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG, 1978 svn_string_create(log_msg, pool)); 1979 *revprop_table_out = new_revprop_table; 1980 return SVN_NO_ERROR; 1981} 1982