140496Sbde/* 250473Speter * commit_util.c: Driver for the WC commit process. 31539Srgrimes * 4114731Sbde * ==================================================================== 51539Srgrimes * Licensed to the Apache Software Foundation (ASF) under one 6156813Sru * or more contributor license agreements. See the NOTICE file 7156813Sru * distributed with this work for additional information 818420Sbde * regarding copyright ownership. The ASF licenses this file 9153838Sdfr * to you under the Apache License, Version 2.0 (the 10133567Stjr * "License"); you may not use this file except in compliance 11133559Stjr * with the License. You may obtain a copy of the License at 12107046Smarcel * 13153838Sdfr * http://www.apache.org/licenses/LICENSE-2.0 14148796Sphk * 15107046Smarcel * Unless required by applicable law or agreed to in writing, 16157236Sjasone * software distributed under the License is distributed on an 17152994Sru * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18113595Snectar * KIND, either express or implied. See the License for the 19153486Sphk * specific language governing permissions and limitations 20107046Smarcel * under the License. 21146261Sru * ==================================================================== 22142582Sru */ 23121640Speter 24133333Sstefanf/* ==================================================================== */ 25133333Sstefanf 26119630Skan 27119630Skan#include <string.h> 281539Srgrimes 29119630Skan#include <apr_pools.h> 3034030Sdufault#include <apr_hash.h> 31152868Srodrigc#include <apr_md5.h> 3234030Sdufault 3388055Sru#include "client.h" 3464767Sjhb#include "svn_dirent_uri.h" 351539Srgrimes#include "svn_path.h" 36146795Srwatson#include "svn_types.h" 37156905Sru#include "svn_pools.h" 38156905Sru#include "svn_props.h" 3983653Speter#include "svn_iter.h" 4017900Speter#include "svn_hash.h" 41146795Srwatson 42147191Sjkoshy#include <assert.h> 43150850Sscottl#include <stdlib.h> /* for qsort() */ 44152317Sdelphij 45140246Sdds#include "svn_private_config.h" 46156905Sru#include "private/svn_wc_private.h" 47140246Sdds#include "private/svn_client_private.h" 48148457Spjd 49148457Spjd/*** Uncomment this to turn on commit driver debugging. ***/ 50140246Sdds/* 51140246Sdds#define SVN_CLIENT_COMMIT_DEBUG 52135339Sglebius*/ 53156885Srwatson 54135339Sglebius/* Wrap an RA error in a nicer error if one is available. */ 55135339Sglebiusstatic svn_error_t * 56137556Smarkmfixup_commit_error(const char *local_abspath, 5777031Sru const char *base_url, 58156813Sru const char *path, 59141397Sphk svn_node_kind_t kind, 60141397Sphk svn_error_t *err, 61141397Sphk svn_client_ctx_t *ctx, 62156813Sru apr_pool_t *scratch_pool) 63148796Sphk{ 64148796Sphk if (err->apr_err == SVN_ERR_FS_NOT_FOUND 65148796Sphk || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS 66156813Sru || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE 67114731Sbde || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND 68125123Semax || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS 69107139Sjulian || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE)) 70156905Sru { 71156905Sru if (ctx->notify_func2) 72156905Sru { 73156905Sru svn_wc_notify_t *notify; 74156905Sru 75156905Sru if (local_abspath) 76156905Sru notify = svn_wc_create_notify(local_abspath, 77156905Sru svn_wc_notify_failed_out_of_date, 78156905Sru scratch_pool); 79156905Sru else 8054351Smarcel notify = svn_wc_create_notify_url( 8154351Smarcel svn_path_url_add_component2(base_url, path, 8254351Smarcel scratch_pool), 8354351Smarcel svn_wc_notify_failed_out_of_date, 8454351Smarcel scratch_pool); 8554351Smarcel 8654351Smarcel notify->kind = kind; 8796462Sru notify->err = err; 8825734Speter 89156532Sru ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 90156532Sru } 9125734Speter 92156532Sru return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err, 93156532Sru (kind == svn_node_dir 94156532Sru ? _("Directory '%s' is out of date") 95156531Sru : _("File '%s' is out of date")), 96156531Sru local_abspath 97156532Sru ? svn_dirent_local_style(local_abspath, 98156531Sru scratch_pool) 99156532Sru : svn_path_url_add_component2(base_url, 100156532Sru path, 10172673Speter scratch_pool)); 10225734Speter } 10388055Sru else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN) 10496462Sru || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH 10517900Speter || err->apr_err == SVN_ERR_RA_NOT_LOCKED) 10688055Sru { 10796462Sru if (ctx->notify_func2) 10817900Speter { 10988055Sru svn_wc_notify_t *notify; 11096462Sru 11134030Sdufault if (local_abspath) 11254351Smarcel notify = svn_wc_create_notify(local_abspath, 113144514Simp svn_wc_notify_failed_locked, 114144514Simp scratch_pool); 115144514Simp else 116144514Simp notify = svn_wc_create_notify_url( 117114731Sbde svn_path_url_add_component2(base_url, path, 118114731Sbde scratch_pool), 119114731Sbde svn_wc_notify_failed_locked, 120114731Sbde scratch_pool); 121114731Sbde 122114731Sbde notify->kind = kind; 123114731Sbde notify->err = err; 124144514Simp 125114731Sbde ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 126114731Sbde } 12754351Smarcel 12854351Smarcel return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err, 129114731Sbde (kind == svn_node_dir 130114731Sbde ? _("Directory '%s' is locked in another working copy") 131114731Sbde : _("File '%s' is locked in another working copy")), 132156813Sru local_abspath 133135851Sdougb ? svn_dirent_local_style(local_abspath, 134135851Sdougb scratch_pool) 135135851Sdougb : svn_path_url_add_component2(base_url, 136135851Sdougb path, 137114731Sbde scratch_pool)); 138114731Sbde } 139144514Simp else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN) 140144514Simp || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE) 141114731Sbde { 142114731Sbde if (ctx->notify_func2) 143114731Sbde { 144114731Sbde svn_wc_notify_t *notify; 145114731Sbde 146114731Sbde if (local_abspath) 147114731Sbde notify = svn_wc_create_notify( 148143013Snjl local_abspath, 14954351Smarcel svn_wc_notify_failed_forbidden_by_server, 150114731Sbde scratch_pool); 151114731Sbde else 15254351Smarcel notify = svn_wc_create_notify_url( 153143013Snjl svn_path_url_add_component2(base_url, path, 154143013Snjl scratch_pool), 155143013Snjl svn_wc_notify_failed_forbidden_by_server, 156123288Sobrien scratch_pool); 157123288Sobrien 158123288Sobrien notify->kind = kind; 159130416Smlaier notify->err = err; 160130416Smlaier 161130416Smlaier ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 162156813Sru } 163116734Sru 164116734Sru return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err, 165116734Sru (kind == svn_node_dir 166145539Sscottl ? _("Changing directory '%s' is forbidden by the server") 167126385Smlaier : _("Changing file '%s' is forbidden by the server")), 168126385Smlaier local_abspath 169126385Smlaier ? svn_dirent_local_style(local_abspath, 170143423Sume scratch_pool) 171143423Sume : svn_path_url_add_component2(base_url, 172143423Sume path, 173116734Sru scratch_pool)); 174116734Sru } 175114731Sbde else 176144514Simp return err; 177114731Sbde} 178114731Sbde 179144514Simp 180144514Simp/*** Harvesting Commit Candidates ***/ 181114731Sbde 182114731Sbde 18377857Sjlemon/* Add a new commit candidate (described by all parameters except 184144514Simp `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's 185144561Simp members are allocated out of RESULT_POOL. 186144561Simp 187144514Simp If the state flag specifies that a lock must be used, store the token in LOCK 188144514Simp in lock_tokens. 189144514Simp */ 190144514Simpstatic svn_error_t * 191144561Simpadd_committable(svn_client__committables_t *committables, 192144561Simp const char *local_abspath, 193144514Simp svn_node_kind_t kind, 194144514Simp const char *repos_root_url, 195144514Simp const char *repos_relpath, 196144514Simp svn_revnum_t revision, 197144514Simp const char *copyfrom_relpath, 1981539Srgrimes svn_revnum_t copyfrom_rev, 19954351Smarcel const char *moved_from_abspath, 20054351Smarcel apr_byte_t state_flags, 20156645Speter apr_hash_t *lock_tokens, 202114731Sbde const svn_lock_t *lock, 203114731Sbde apr_pool_t *result_pool, 204114731Sbde apr_pool_t *scratch_pool) 205114731Sbde{ 20654351Smarcel apr_array_header_t *array; 207143013Snjl svn_client_commit_item3_t *new_item; 208114731Sbde 209114731Sbde /* Sanity checks. */ 210114731Sbde SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 211114731Sbde SVN_ERR_ASSERT(repos_root_url && repos_relpath); 21277046Sru 213143013Snjl /* ### todo: Get the canonical repository for this item, which will 214143013Snjl be the real key for the COMMITTABLES hash, instead of the above 215143013Snjl bogosity. */ 216143013Snjl array = svn_hash_gets(committables->by_repository, repos_root_url); 217143013Snjl 218142992Sru /* E-gads! There is no array for this repository yet! Oh, no 219142992Sru problem, we'll just create (and add to the hash) one. */ 220142992Sru if (array == NULL) 221142992Sru { 222142992Sru array = apr_array_make(result_pool, 1, sizeof(new_item)); 223114731Sbde svn_hash_sets(committables->by_repository, 224114731Sbde apr_pstrdup(result_pool, repos_root_url), array); 225114731Sbde } 226114731Sbde 227114731Sbde /* Now update pointer values, ensuring that their allocations live 228114731Sbde in POOL. */ 229130416Smlaier new_item = svn_client_commit_item3_create(result_pool); 230130416Smlaier new_item->path = apr_pstrdup(result_pool, local_abspath); 231130416Smlaier new_item->kind = kind; 232130416Smlaier new_item->url = svn_path_url_add_component2(repos_root_url, 233130416Smlaier repos_relpath, 234156813Sru result_pool); 235116734Sru new_item->revision = revision; 236116734Sru new_item->copyfrom_url = copyfrom_relpath 237116734Sru ? svn_path_url_add_component2(repos_root_url, 238116734Sru copyfrom_relpath, 239116734Sru result_pool) 240145539Sscottl : NULL; 241126385Smlaier new_item->copyfrom_rev = copyfrom_rev; 242126385Smlaier new_item->state_flags = state_flags; 243126385Smlaier new_item->incoming_prop_changes = apr_array_make(result_pool, 1, 244126385Smlaier sizeof(svn_prop_t *)); 245126385Smlaier 246143423Sume if (moved_from_abspath) 247143423Sume new_item->moved_from_abspath = apr_pstrdup(result_pool, 248143423Sume moved_from_abspath); 249143423Sume 250143423Sume /* Now, add the commit item to the array. */ 251114731Sbde APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item; 252114731Sbde 253114731Sbde /* ... and to the hash. */ 254114731Sbde svn_hash_sets(committables->by_path, new_item->path, new_item); 255114731Sbde 256144514Simp if (lock 257114731Sbde && lock_tokens 258144514Simp && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)) 259114731Sbde { 260114731Sbde svn_hash_sets(lock_tokens, new_item->url, 261144514Simp apr_pstrdup(result_pool, lock->token)); 262144514Simp } 263114731Sbde 264144514Simp return SVN_NO_ERROR; 265114731Sbde} 266114731Sbde 267114731Sbde/* If there is a commit item for PATH in COMMITTABLES, return it, else 268144514Simp return NULL. Use POOL for temporary allocation only. */ 269144561Simpstatic svn_client_commit_item3_t * 270144561Simplook_up_committable(svn_client__committables_t *committables, 271144514Simp const char *path, 272144514Simp apr_pool_t *pool) 273144514Simp{ 274144514Simp return (svn_client_commit_item3_t *) 275144514Simp svn_hash_gets(committables->by_path, path); 276144514Simp} 277144561Simp 278144561Simp/* Helper function for svn_client__harvest_committables(). 279144514Simp * Determine whether we are within a tree-conflicted subtree of the 280144514Simp * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */ 281144514Simpstatic svn_error_t * 282144514Simpbail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx, 283144514Simp const char *local_abspath, 284144514Simp svn_wc_notify_func2_t notify_func, 285144514Simp 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