1251881Speter/* 2251881Speter * commit_util.c: Driver for the WC commit process. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter#include <string.h> 28251881Speter 29251881Speter#include <apr_pools.h> 30251881Speter#include <apr_hash.h> 31251881Speter#include <apr_md5.h> 32251881Speter 33251881Speter#include "client.h" 34251881Speter#include "svn_dirent_uri.h" 35251881Speter#include "svn_path.h" 36251881Speter#include "svn_types.h" 37251881Speter#include "svn_pools.h" 38251881Speter#include "svn_props.h" 39251881Speter#include "svn_iter.h" 40251881Speter#include "svn_hash.h" 41251881Speter 42251881Speter#include <assert.h> 43251881Speter 44251881Speter#include "svn_private_config.h" 45251881Speter#include "private/svn_wc_private.h" 46251881Speter#include "private/svn_client_private.h" 47299742Sdim#include "private/svn_sorts_private.h" 48251881Speter 49251881Speter/*** Uncomment this to turn on commit driver debugging. ***/ 50251881Speter/* 51251881Speter#define SVN_CLIENT_COMMIT_DEBUG 52251881Speter*/ 53251881Speter 54251881Speter/* Wrap an RA error in a nicer error if one is available. */ 55251881Speterstatic svn_error_t * 56251881Speterfixup_commit_error(const char *local_abspath, 57251881Speter const char *base_url, 58251881Speter const char *path, 59251881Speter svn_node_kind_t kind, 60251881Speter svn_error_t *err, 61251881Speter svn_client_ctx_t *ctx, 62251881Speter apr_pool_t *scratch_pool) 63251881Speter{ 64251881Speter if (err->apr_err == SVN_ERR_FS_NOT_FOUND 65299742Sdim || err->apr_err == SVN_ERR_FS_CONFLICT 66251881Speter || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS 67251881Speter || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE 68251881Speter || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND 69251881Speter || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS 70299742Sdim || err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED 71251881Speter || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE)) 72251881Speter { 73251881Speter if (ctx->notify_func2) 74251881Speter { 75251881Speter svn_wc_notify_t *notify; 76251881Speter 77251881Speter if (local_abspath) 78251881Speter notify = svn_wc_create_notify(local_abspath, 79251881Speter svn_wc_notify_failed_out_of_date, 80251881Speter scratch_pool); 81251881Speter else 82251881Speter notify = svn_wc_create_notify_url( 83251881Speter svn_path_url_add_component2(base_url, path, 84251881Speter scratch_pool), 85251881Speter svn_wc_notify_failed_out_of_date, 86251881Speter scratch_pool); 87251881Speter 88251881Speter notify->kind = kind; 89251881Speter notify->err = err; 90251881Speter 91251881Speter ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 92251881Speter } 93251881Speter 94251881Speter return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err, 95251881Speter (kind == svn_node_dir 96251881Speter ? _("Directory '%s' is out of date") 97251881Speter : _("File '%s' is out of date")), 98251881Speter local_abspath 99251881Speter ? svn_dirent_local_style(local_abspath, 100251881Speter scratch_pool) 101251881Speter : svn_path_url_add_component2(base_url, 102251881Speter path, 103251881Speter scratch_pool)); 104251881Speter } 105251881Speter else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN) 106251881Speter || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH 107299742Sdim || err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN 108251881Speter || err->apr_err == SVN_ERR_RA_NOT_LOCKED) 109251881Speter { 110251881Speter if (ctx->notify_func2) 111251881Speter { 112251881Speter svn_wc_notify_t *notify; 113251881Speter 114251881Speter if (local_abspath) 115251881Speter notify = svn_wc_create_notify(local_abspath, 116251881Speter svn_wc_notify_failed_locked, 117251881Speter scratch_pool); 118251881Speter else 119251881Speter notify = svn_wc_create_notify_url( 120251881Speter svn_path_url_add_component2(base_url, path, 121251881Speter scratch_pool), 122251881Speter svn_wc_notify_failed_locked, 123251881Speter scratch_pool); 124251881Speter 125251881Speter notify->kind = kind; 126251881Speter notify->err = err; 127251881Speter 128251881Speter ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 129251881Speter } 130251881Speter 131251881Speter return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err, 132251881Speter (kind == svn_node_dir 133251881Speter ? _("Directory '%s' is locked in another working copy") 134251881Speter : _("File '%s' is locked in another working copy")), 135251881Speter local_abspath 136251881Speter ? svn_dirent_local_style(local_abspath, 137251881Speter scratch_pool) 138251881Speter : svn_path_url_add_component2(base_url, 139251881Speter path, 140251881Speter scratch_pool)); 141251881Speter } 142251881Speter else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN) 143251881Speter || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE) 144251881Speter { 145251881Speter if (ctx->notify_func2) 146251881Speter { 147251881Speter svn_wc_notify_t *notify; 148251881Speter 149251881Speter if (local_abspath) 150251881Speter notify = svn_wc_create_notify( 151251881Speter local_abspath, 152251881Speter svn_wc_notify_failed_forbidden_by_server, 153251881Speter scratch_pool); 154251881Speter else 155251881Speter notify = svn_wc_create_notify_url( 156251881Speter svn_path_url_add_component2(base_url, path, 157251881Speter scratch_pool), 158251881Speter svn_wc_notify_failed_forbidden_by_server, 159251881Speter scratch_pool); 160251881Speter 161251881Speter notify->kind = kind; 162251881Speter notify->err = err; 163251881Speter 164251881Speter ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 165251881Speter } 166251881Speter 167251881Speter return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err, 168251881Speter (kind == svn_node_dir 169251881Speter ? _("Changing directory '%s' is forbidden by the server") 170251881Speter : _("Changing file '%s' is forbidden by the server")), 171251881Speter local_abspath 172251881Speter ? svn_dirent_local_style(local_abspath, 173251881Speter scratch_pool) 174251881Speter : svn_path_url_add_component2(base_url, 175251881Speter path, 176251881Speter scratch_pool)); 177251881Speter } 178251881Speter else 179251881Speter return err; 180251881Speter} 181251881Speter 182251881Speter 183251881Speter/*** Harvesting Commit Candidates ***/ 184251881Speter 185251881Speter 186251881Speter/* Add a new commit candidate (described by all parameters except 187251881Speter `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's 188251881Speter members are allocated out of RESULT_POOL. 189251881Speter 190251881Speter If the state flag specifies that a lock must be used, store the token in LOCK 191251881Speter in lock_tokens. 192251881Speter */ 193251881Speterstatic svn_error_t * 194251881Speteradd_committable(svn_client__committables_t *committables, 195251881Speter const char *local_abspath, 196251881Speter svn_node_kind_t kind, 197251881Speter const char *repos_root_url, 198251881Speter const char *repos_relpath, 199251881Speter svn_revnum_t revision, 200251881Speter const char *copyfrom_relpath, 201251881Speter svn_revnum_t copyfrom_rev, 202251881Speter const char *moved_from_abspath, 203251881Speter apr_byte_t state_flags, 204251881Speter apr_hash_t *lock_tokens, 205251881Speter const svn_lock_t *lock, 206251881Speter apr_pool_t *result_pool, 207251881Speter apr_pool_t *scratch_pool) 208251881Speter{ 209251881Speter apr_array_header_t *array; 210251881Speter svn_client_commit_item3_t *new_item; 211251881Speter 212251881Speter /* Sanity checks. */ 213251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 214251881Speter SVN_ERR_ASSERT(repos_root_url && repos_relpath); 215251881Speter 216251881Speter /* ### todo: Get the canonical repository for this item, which will 217251881Speter be the real key for the COMMITTABLES hash, instead of the above 218251881Speter bogosity. */ 219251881Speter array = svn_hash_gets(committables->by_repository, repos_root_url); 220251881Speter 221251881Speter /* E-gads! There is no array for this repository yet! Oh, no 222251881Speter problem, we'll just create (and add to the hash) one. */ 223251881Speter if (array == NULL) 224251881Speter { 225251881Speter array = apr_array_make(result_pool, 1, sizeof(new_item)); 226251881Speter svn_hash_sets(committables->by_repository, 227251881Speter apr_pstrdup(result_pool, repos_root_url), array); 228251881Speter } 229251881Speter 230251881Speter /* Now update pointer values, ensuring that their allocations live 231251881Speter in POOL. */ 232251881Speter new_item = svn_client_commit_item3_create(result_pool); 233251881Speter new_item->path = apr_pstrdup(result_pool, local_abspath); 234251881Speter new_item->kind = kind; 235251881Speter new_item->url = svn_path_url_add_component2(repos_root_url, 236251881Speter repos_relpath, 237251881Speter result_pool); 238251881Speter new_item->revision = revision; 239251881Speter new_item->copyfrom_url = copyfrom_relpath 240251881Speter ? svn_path_url_add_component2(repos_root_url, 241251881Speter copyfrom_relpath, 242251881Speter result_pool) 243251881Speter : NULL; 244251881Speter new_item->copyfrom_rev = copyfrom_rev; 245251881Speter new_item->state_flags = state_flags; 246251881Speter new_item->incoming_prop_changes = apr_array_make(result_pool, 1, 247251881Speter sizeof(svn_prop_t *)); 248251881Speter 249251881Speter if (moved_from_abspath) 250251881Speter new_item->moved_from_abspath = apr_pstrdup(result_pool, 251251881Speter moved_from_abspath); 252251881Speter 253251881Speter /* Now, add the commit item to the array. */ 254251881Speter APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item; 255251881Speter 256251881Speter /* ... and to the hash. */ 257251881Speter svn_hash_sets(committables->by_path, new_item->path, new_item); 258251881Speter 259251881Speter if (lock 260251881Speter && lock_tokens 261251881Speter && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)) 262251881Speter { 263251881Speter svn_hash_sets(lock_tokens, new_item->url, 264251881Speter apr_pstrdup(result_pool, lock->token)); 265251881Speter } 266251881Speter 267251881Speter return SVN_NO_ERROR; 268251881Speter} 269251881Speter 270251881Speter/* If there is a commit item for PATH in COMMITTABLES, return it, else 271251881Speter return NULL. Use POOL for temporary allocation only. */ 272251881Speterstatic svn_client_commit_item3_t * 273251881Speterlook_up_committable(svn_client__committables_t *committables, 274251881Speter const char *path, 275251881Speter apr_pool_t *pool) 276251881Speter{ 277251881Speter return (svn_client_commit_item3_t *) 278251881Speter svn_hash_gets(committables->by_path, path); 279251881Speter} 280251881Speter 281251881Speter/* Helper function for svn_client__harvest_committables(). 282251881Speter * Determine whether we are within a tree-conflicted subtree of the 283251881Speter * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */ 284251881Speterstatic svn_error_t * 285251881Speterbail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx, 286251881Speter const char *local_abspath, 287251881Speter svn_wc_notify_func2_t notify_func, 288251881Speter void *notify_baton, 289251881Speter apr_pool_t *scratch_pool) 290251881Speter{ 291251881Speter const char *wcroot_abspath; 292251881Speter 293251881Speter SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath, 294251881Speter scratch_pool, scratch_pool)); 295251881Speter 296251881Speter local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 297251881Speter 298251881Speter while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath)) 299251881Speter { 300251881Speter svn_boolean_t tree_conflicted; 301251881Speter 302251881Speter /* Check if the parent has tree conflicts */ 303251881Speter SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, 304251881Speter wc_ctx, local_abspath, scratch_pool)); 305251881Speter if (tree_conflicted) 306251881Speter { 307251881Speter if (notify_func != NULL) 308251881Speter { 309251881Speter notify_func(notify_baton, 310251881Speter svn_wc_create_notify(local_abspath, 311251881Speter svn_wc_notify_failed_conflict, 312251881Speter scratch_pool), 313251881Speter scratch_pool); 314251881Speter } 315251881Speter 316251881Speter return svn_error_createf( 317251881Speter SVN_ERR_WC_FOUND_CONFLICT, NULL, 318251881Speter _("Aborting commit: '%s' remains in tree-conflict"), 319251881Speter svn_dirent_local_style(local_abspath, scratch_pool)); 320251881Speter } 321251881Speter 322251881Speter /* Step outwards */ 323251881Speter if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) 324251881Speter break; 325251881Speter else 326251881Speter local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 327251881Speter } 328251881Speter 329251881Speter return SVN_NO_ERROR; 330251881Speter} 331251881Speter 332251881Speter 333251881Speter/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using 334251881Speter WC_CTX and add those candidates to COMMITTABLES. If in ADDS_ONLY modes, 335251881Speter only new additions are recognized. 336251881Speter 337251881Speter DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH 338251881Speter when LOCAL_ABSPATH is itself a directory; see 339251881Speter svn_client__harvest_committables() for its behavior. 340251881Speter 341251881Speter Lock tokens of candidates will be added to LOCK_TOKENS, if 342251881Speter non-NULL. JUST_LOCKED indicates whether to treat non-modified items with 343251881Speter lock tokens as commit candidates. 344251881Speter 345251881Speter If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to 346251881Speter be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as 347251881Speter items to delete in the copy destination. COPY_MODE_ROOT should be set TRUE 348251881Speter for the first call for which COPY_MODE is TRUE, i.e. not for the 349251881Speter recursive calls, and FALSE otherwise. 350251881Speter 351251881Speter If CHANGELISTS is non-NULL, it is a hash whose keys are const char * 352251881Speter changelist names used as a restrictive filter 353251881Speter when harvesting committables; that is, don't add a path to 354251881Speter COMMITTABLES unless it's a member of one of those changelists. 355251881Speter 356251881Speter IS_EXPLICIT_TARGET should always be passed as TRUE, except when 357251881Speter harvest_committables() calls itself in recursion. This provides a way to 358251881Speter tell whether LOCAL_ABSPATH was an original target or whether it was reached 359251881Speter by recursing deeper into a dir target. (This is used to skip all file 360251881Speter externals that aren't explicit commit targets.) 361251881Speter 362251881Speter DANGLERS is a hash table mapping const char* absolute paths of a parent 363251881Speter to a const char * absolute path of a child. See the comment about 364251881Speter danglers at the top of svn_client__harvest_committables(). 365251881Speter 366251881Speter If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see 367251881Speter if the user has cancelled the operation. 368251881Speter 369251881Speter Any items added to COMMITTABLES are allocated from the COMITTABLES 370251881Speter hash pool, not POOL. SCRATCH_POOL is used for temporary allocations. */ 371251881Speter 372251881Speterstruct harvest_baton 373251881Speter{ 374251881Speter /* Static data */ 375251881Speter const char *root_abspath; 376251881Speter svn_client__committables_t *committables; 377251881Speter apr_hash_t *lock_tokens; 378251881Speter const char *commit_relpath; /* Valid for the harvest root */ 379251881Speter svn_depth_t depth; 380251881Speter svn_boolean_t just_locked; 381251881Speter apr_hash_t *changelists; 382251881Speter apr_hash_t *danglers; 383251881Speter svn_client__check_url_kind_t check_url_func; 384251881Speter void *check_url_baton; 385251881Speter svn_wc_notify_func2_t notify_func; 386251881Speter void *notify_baton; 387251881Speter svn_wc_context_t *wc_ctx; 388251881Speter apr_pool_t *result_pool; 389251881Speter 390251881Speter /* Harvester state */ 391251881Speter const char *skip_below_abspath; /* If non-NULL, skip everything below */ 392251881Speter}; 393251881Speter 394251881Speterstatic svn_error_t * 395251881Speterharvest_status_callback(void *status_baton, 396251881Speter const char *local_abspath, 397251881Speter const svn_wc_status3_t *status, 398251881Speter apr_pool_t *scratch_pool); 399251881Speter 400251881Speterstatic svn_error_t * 401251881Speterharvest_committables(const char *local_abspath, 402251881Speter svn_client__committables_t *committables, 403251881Speter apr_hash_t *lock_tokens, 404251881Speter const char *copy_mode_relpath, 405251881Speter svn_depth_t depth, 406251881Speter svn_boolean_t just_locked, 407251881Speter apr_hash_t *changelists, 408251881Speter apr_hash_t *danglers, 409251881Speter svn_client__check_url_kind_t check_url_func, 410251881Speter void *check_url_baton, 411251881Speter svn_cancel_func_t cancel_func, 412251881Speter void *cancel_baton, 413251881Speter svn_wc_notify_func2_t notify_func, 414251881Speter void *notify_baton, 415251881Speter svn_wc_context_t *wc_ctx, 416251881Speter apr_pool_t *result_pool, 417251881Speter apr_pool_t *scratch_pool) 418251881Speter{ 419251881Speter struct harvest_baton baton; 420251881Speter 421251881Speter SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked); 422251881Speter 423251881Speter baton.root_abspath = local_abspath; 424251881Speter baton.committables = committables; 425251881Speter baton.lock_tokens = lock_tokens; 426251881Speter baton.commit_relpath = copy_mode_relpath; 427251881Speter baton.depth = depth; 428251881Speter baton.just_locked = just_locked; 429251881Speter baton.changelists = changelists; 430251881Speter baton.danglers = danglers; 431251881Speter baton.check_url_func = check_url_func; 432251881Speter baton.check_url_baton = check_url_baton; 433251881Speter baton.notify_func = notify_func; 434251881Speter baton.notify_baton = notify_baton; 435251881Speter baton.wc_ctx = wc_ctx; 436251881Speter baton.result_pool = result_pool; 437251881Speter 438251881Speter baton.skip_below_abspath = NULL; 439251881Speter 440251881Speter SVN_ERR(svn_wc_walk_status(wc_ctx, 441251881Speter local_abspath, 442251881Speter depth, 443251881Speter (copy_mode_relpath != NULL) /* get_all */, 444251881Speter FALSE /* no_ignore */, 445251881Speter FALSE /* ignore_text_mods */, 446251881Speter NULL /* ignore_patterns */, 447251881Speter harvest_status_callback, 448251881Speter &baton, 449251881Speter cancel_func, cancel_baton, 450251881Speter scratch_pool)); 451251881Speter 452251881Speter return SVN_NO_ERROR; 453251881Speter} 454251881Speter 455251881Speterstatic svn_error_t * 456251881Speterharvest_not_present_for_copy(svn_wc_context_t *wc_ctx, 457251881Speter const char *local_abspath, 458251881Speter svn_client__committables_t *committables, 459251881Speter const char *repos_root_url, 460251881Speter const char *commit_relpath, 461251881Speter svn_client__check_url_kind_t check_url_func, 462251881Speter void *check_url_baton, 463251881Speter apr_pool_t *result_pool, 464251881Speter apr_pool_t *scratch_pool) 465251881Speter{ 466251881Speter const apr_array_header_t *children; 467251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 468251881Speter int i; 469251881Speter 470299742Sdim SVN_ERR_ASSERT(commit_relpath != NULL); 471299742Sdim 472251881Speter /* A function to retrieve not present children would be nice to have */ 473299742Sdim SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx, 474299742Sdim local_abspath, 475299742Sdim scratch_pool, iterpool)); 476251881Speter 477251881Speter for (i = 0; i < children->nelts; i++) 478251881Speter { 479251881Speter const char *this_abspath = APR_ARRAY_IDX(children, i, const char *); 480251881Speter const char *name = svn_dirent_basename(this_abspath, NULL); 481251881Speter const char *this_commit_relpath; 482251881Speter svn_boolean_t not_present; 483251881Speter svn_node_kind_t kind; 484251881Speter 485251881Speter svn_pool_clear(iterpool); 486251881Speter 487251881Speter SVN_ERR(svn_wc__node_is_not_present(¬_present, NULL, NULL, wc_ctx, 488251881Speter this_abspath, FALSE, scratch_pool)); 489251881Speter 490251881Speter if (!not_present) 491299742Sdim continue; /* Node is replaced */ 492251881Speter 493299742Sdim this_commit_relpath = svn_relpath_join(commit_relpath, name, 494299742Sdim iterpool); 495251881Speter 496251881Speter /* We should check if we should really add a delete operation */ 497251881Speter if (check_url_func) 498251881Speter { 499251881Speter svn_revnum_t parent_rev; 500251881Speter const char *parent_repos_relpath; 501251881Speter const char *parent_repos_root_url; 502251881Speter const char *node_url; 503251881Speter 504251881Speter /* Determine from what parent we would be the deleted child */ 505251881Speter SVN_ERR(svn_wc__node_get_origin( 506251881Speter NULL, &parent_rev, &parent_repos_relpath, 507299742Sdim &parent_repos_root_url, NULL, NULL, NULL, 508251881Speter wc_ctx, 509251881Speter svn_dirent_dirname(this_abspath, 510251881Speter scratch_pool), 511251881Speter FALSE, scratch_pool, scratch_pool)); 512251881Speter 513251881Speter node_url = svn_path_url_add_component2( 514251881Speter svn_path_url_add_component2(parent_repos_root_url, 515251881Speter parent_repos_relpath, 516251881Speter scratch_pool), 517251881Speter svn_dirent_basename(this_abspath, NULL), 518251881Speter iterpool); 519251881Speter 520251881Speter SVN_ERR(check_url_func(check_url_baton, &kind, 521251881Speter node_url, parent_rev, iterpool)); 522251881Speter 523251881Speter if (kind == svn_node_none) 524251881Speter continue; /* This node can't be deleted */ 525251881Speter } 526251881Speter else 527251881Speter SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath, 528251881Speter TRUE, TRUE, scratch_pool)); 529251881Speter 530251881Speter SVN_ERR(add_committable(committables, this_abspath, kind, 531251881Speter repos_root_url, 532251881Speter this_commit_relpath, 533251881Speter SVN_INVALID_REVNUM, 534251881Speter NULL /* copyfrom_relpath */, 535251881Speter SVN_INVALID_REVNUM /* copyfrom_rev */, 536251881Speter NULL /* moved_from_abspath */, 537251881Speter SVN_CLIENT_COMMIT_ITEM_DELETE, 538251881Speter NULL, NULL, 539251881Speter result_pool, scratch_pool)); 540251881Speter } 541251881Speter 542251881Speter svn_pool_destroy(iterpool); 543251881Speter return SVN_NO_ERROR; 544251881Speter} 545251881Speter 546251881Speter/* Implements svn_wc_status_func4_t */ 547251881Speterstatic svn_error_t * 548251881Speterharvest_status_callback(void *status_baton, 549251881Speter const char *local_abspath, 550251881Speter const svn_wc_status3_t *status, 551251881Speter apr_pool_t *scratch_pool) 552251881Speter{ 553251881Speter apr_byte_t state_flags = 0; 554251881Speter svn_revnum_t node_rev; 555251881Speter const char *cf_relpath = NULL; 556251881Speter svn_revnum_t cf_rev = SVN_INVALID_REVNUM; 557251881Speter svn_boolean_t matches_changelists; 558251881Speter svn_boolean_t is_added; 559251881Speter svn_boolean_t is_deleted; 560251881Speter svn_boolean_t is_replaced; 561251881Speter svn_boolean_t is_op_root; 562251881Speter svn_revnum_t original_rev; 563251881Speter const char *original_relpath; 564251881Speter svn_boolean_t copy_mode; 565251881Speter 566251881Speter struct harvest_baton *baton = status_baton; 567251881Speter svn_boolean_t is_harvest_root = 568251881Speter (strcmp(baton->root_abspath, local_abspath) == 0); 569251881Speter svn_client__committables_t *committables = baton->committables; 570251881Speter const char *repos_root_url = status->repos_root_url; 571251881Speter const char *commit_relpath = NULL; 572251881Speter svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root); 573251881Speter svn_boolean_t just_locked = baton->just_locked; 574251881Speter apr_hash_t *changelists = baton->changelists; 575251881Speter svn_wc_notify_func2_t notify_func = baton->notify_func; 576251881Speter void *notify_baton = baton->notify_baton; 577251881Speter svn_wc_context_t *wc_ctx = baton->wc_ctx; 578251881Speter apr_pool_t *result_pool = baton->result_pool; 579251881Speter const char *moved_from_abspath = NULL; 580251881Speter 581251881Speter if (baton->commit_relpath) 582251881Speter commit_relpath = svn_relpath_join( 583251881Speter baton->commit_relpath, 584251881Speter svn_dirent_skip_ancestor(baton->root_abspath, 585251881Speter local_abspath), 586251881Speter scratch_pool); 587251881Speter 588251881Speter copy_mode = (commit_relpath != NULL); 589251881Speter 590251881Speter if (baton->skip_below_abspath 591251881Speter && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath)) 592251881Speter { 593251881Speter return SVN_NO_ERROR; 594251881Speter } 595251881Speter else 596251881Speter baton->skip_below_abspath = NULL; /* We have left the skip tree */ 597251881Speter 598251881Speter /* Return early for nodes that don't have a committable status */ 599251881Speter switch (status->node_status) 600251881Speter { 601251881Speter case svn_wc_status_unversioned: 602251881Speter case svn_wc_status_ignored: 603251881Speter case svn_wc_status_external: 604251881Speter case svn_wc_status_none: 605251881Speter /* Unversioned nodes aren't committable, but are reported by the status 606251881Speter walker. 607251881Speter But if the unversioned node is the root of the walk, we have a user 608251881Speter error */ 609251881Speter if (is_harvest_root) 610251881Speter return svn_error_createf( 611251881Speter SVN_ERR_ILLEGAL_TARGET, NULL, 612251881Speter _("'%s' is not under version control"), 613251881Speter svn_dirent_local_style(local_abspath, scratch_pool)); 614251881Speter return SVN_NO_ERROR; 615251881Speter case svn_wc_status_normal: 616251881Speter /* Status normal nodes aren't modified, so we don't have to commit them 617251881Speter when we perform a normal commit. But if a node is conflicted we want 618251881Speter to stop the commit and if we are collecting lock tokens we want to 619251881Speter look further anyway. 620251881Speter 621251881Speter When in copy mode we need to compare the revision of the node against 622251881Speter the parent node to copy mixed-revision base nodes properly */ 623251881Speter if (!copy_mode && !status->conflicted 624251881Speter && !(just_locked && status->lock)) 625251881Speter return SVN_NO_ERROR; 626251881Speter break; 627251881Speter default: 628251881Speter /* Fall through */ 629251881Speter break; 630251881Speter } 631251881Speter 632251881Speter /* Early out if the item is already marked as committable. */ 633251881Speter if (look_up_committable(committables, local_abspath, scratch_pool)) 634251881Speter return SVN_NO_ERROR; 635251881Speter 636251881Speter SVN_ERR_ASSERT((copy_mode && commit_relpath) 637251881Speter || (! copy_mode && ! commit_relpath)); 638251881Speter SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root); 639251881Speter 640251881Speter /* Save the result for reuse. */ 641251881Speter matches_changelists = ((changelists == NULL) 642251881Speter || (status->changelist != NULL 643251881Speter && svn_hash_gets(changelists, status->changelist) 644251881Speter != NULL)); 645251881Speter 646251881Speter /* Early exit. */ 647251881Speter if (status->kind != svn_node_dir && ! matches_changelists) 648251881Speter { 649251881Speter return SVN_NO_ERROR; 650251881Speter } 651251881Speter 652251881Speter /* If NODE is in our changelist, then examine it for conflicts. We 653251881Speter need to bail out if any conflicts exist. 654251881Speter The status walker checked for conflict marker removal. */ 655251881Speter if (status->conflicted && matches_changelists) 656251881Speter { 657251881Speter if (notify_func != NULL) 658251881Speter { 659251881Speter notify_func(notify_baton, 660251881Speter svn_wc_create_notify(local_abspath, 661251881Speter svn_wc_notify_failed_conflict, 662251881Speter scratch_pool), 663251881Speter scratch_pool); 664251881Speter } 665251881Speter 666251881Speter return svn_error_createf( 667251881Speter SVN_ERR_WC_FOUND_CONFLICT, NULL, 668251881Speter _("Aborting commit: '%s' remains in conflict"), 669251881Speter svn_dirent_local_style(local_abspath, scratch_pool)); 670251881Speter } 671251881Speter else if (status->node_status == svn_wc_status_obstructed) 672251881Speter { 673251881Speter /* A node's type has changed before attempting to commit. 674251881Speter This also catches symlink vs non symlink changes */ 675251881Speter 676251881Speter if (notify_func != NULL) 677251881Speter { 678251881Speter notify_func(notify_baton, 679251881Speter svn_wc_create_notify(local_abspath, 680251881Speter svn_wc_notify_failed_obstruction, 681251881Speter scratch_pool), 682251881Speter scratch_pool); 683251881Speter } 684251881Speter 685251881Speter return svn_error_createf( 686251881Speter SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 687251881Speter _("Node '%s' has unexpectedly changed kind"), 688251881Speter svn_dirent_local_style(local_abspath, scratch_pool)); 689251881Speter } 690251881Speter 691251881Speter if (status->conflicted && status->kind == svn_node_unknown) 692251881Speter return SVN_NO_ERROR; /* Ignore delete-delete conflict */ 693251881Speter 694251881Speter /* Return error on unknown path kinds. We check both the entry and 695251881Speter the node itself, since a path might have changed kind since its 696251881Speter entry was written. */ 697251881Speter SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted, 698251881Speter &is_replaced, 699251881Speter &is_op_root, 700251881Speter &node_rev, 701251881Speter &original_rev, &original_relpath, 702251881Speter wc_ctx, local_abspath, 703251881Speter scratch_pool, scratch_pool)); 704251881Speter 705251881Speter /* Hande file externals only when passed as explicit target. Note that 706251881Speter * svn_client_commit6() passes all committable externals in as explicit 707251881Speter * targets iff they count. */ 708251881Speter if (status->file_external && !is_harvest_root) 709251881Speter { 710251881Speter return SVN_NO_ERROR; 711251881Speter } 712251881Speter 713251881Speter if (status->node_status == svn_wc_status_missing && matches_changelists) 714251881Speter { 715251881Speter /* Added files and directories must exist. See issue #3198. */ 716251881Speter if (is_added && is_op_root) 717251881Speter { 718251881Speter if (notify_func != NULL) 719251881Speter { 720251881Speter notify_func(notify_baton, 721251881Speter svn_wc_create_notify(local_abspath, 722251881Speter svn_wc_notify_failed_missing, 723251881Speter scratch_pool), 724251881Speter scratch_pool); 725251881Speter } 726251881Speter return svn_error_createf( 727251881Speter SVN_ERR_WC_PATH_NOT_FOUND, NULL, 728251881Speter _("'%s' is scheduled for addition, but is missing"), 729251881Speter svn_dirent_local_style(local_abspath, scratch_pool)); 730251881Speter } 731251881Speter 732251881Speter return SVN_NO_ERROR; 733251881Speter } 734251881Speter 735251881Speter if (is_deleted && !is_op_root /* && !is_added */) 736251881Speter return SVN_NO_ERROR; /* Not an operational delete and not an add. */ 737251881Speter 738251881Speter /* Check for the deletion case. 739251881Speter * We delete explicitly deleted nodes (duh!) 740251881Speter * We delete not-present children of copies 741251881Speter * We delete nodes that directly replace a node in its ancestor 742251881Speter */ 743251881Speter 744251881Speter if (is_deleted || is_replaced) 745251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 746251881Speter 747251881Speter /* Check for adds and copies */ 748251881Speter if (is_added && is_op_root) 749251881Speter { 750251881Speter /* Root of local add or copy */ 751251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD; 752251881Speter 753251881Speter if (original_relpath) 754251881Speter { 755251881Speter /* Root of copy */ 756251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY; 757251881Speter cf_relpath = original_relpath; 758251881Speter cf_rev = original_rev; 759251881Speter 760251881Speter if (status->moved_from_abspath && !copy_mode) 761251881Speter { 762251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE; 763251881Speter moved_from_abspath = status->moved_from_abspath; 764251881Speter } 765251881Speter } 766251881Speter } 767251881Speter 768251881Speter /* Further copies may occur in copy mode. */ 769251881Speter else if (copy_mode 770251881Speter && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)) 771251881Speter { 772251881Speter svn_revnum_t dir_rev = SVN_INVALID_REVNUM; 773299742Sdim const char *dir_repos_relpath = NULL; 774251881Speter 775299742Sdim if (!copy_mode_root && !is_added) 776299742Sdim SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, &dir_repos_relpath, NULL, 777299742Sdim NULL, NULL, 778251881Speter wc_ctx, svn_dirent_dirname(local_abspath, 779251881Speter scratch_pool), 780251881Speter FALSE /* ignore_enoent */, 781251881Speter scratch_pool, scratch_pool)); 782251881Speter 783251881Speter if (copy_mode_root || status->switched || node_rev != dir_rev) 784251881Speter { 785251881Speter state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD 786251881Speter | SVN_CLIENT_COMMIT_ITEM_IS_COPY); 787251881Speter 788251881Speter if (status->copied) 789251881Speter { 790251881Speter /* Copy from original location */ 791251881Speter cf_rev = original_rev; 792251881Speter cf_relpath = original_relpath; 793251881Speter } 794251881Speter else 795251881Speter { 796251881Speter /* Copy BASE location, to represent a mixed-rev or switch copy */ 797251881Speter cf_rev = status->revision; 798251881Speter cf_relpath = status->repos_relpath; 799251881Speter } 800299742Sdim 801299742Sdim if (!copy_mode_root && !is_added && baton->check_url_func 802299742Sdim && dir_repos_relpath) 803299742Sdim { 804299742Sdim svn_node_kind_t me_kind; 805299742Sdim /* Maybe we need to issue an delete (mixed rev/switched) */ 806299742Sdim 807299742Sdim SVN_ERR(baton->check_url_func( 808299742Sdim baton->check_url_baton, &me_kind, 809299742Sdim svn_path_url_add_component2(repos_root_url, 810299742Sdim svn_relpath_join(dir_repos_relpath, 811299742Sdim svn_dirent_basename(local_abspath, 812299742Sdim NULL), 813299742Sdim scratch_pool), 814299742Sdim scratch_pool), 815299742Sdim dir_rev, scratch_pool)); 816299742Sdim if (me_kind != svn_node_none) 817299742Sdim state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 818299742Sdim } 819251881Speter } 820251881Speter } 821251881Speter 822251881Speter if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 823251881Speter || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 824251881Speter { 825251881Speter svn_boolean_t text_mod = FALSE; 826251881Speter svn_boolean_t prop_mod = FALSE; 827251881Speter 828251881Speter if (status->kind == svn_node_file) 829251881Speter { 830251881Speter /* Check for text modifications on files */ 831251881Speter if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 832251881Speter && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 833251881Speter { 834251881Speter text_mod = TRUE; /* Local added files are always modified */ 835251881Speter } 836251881Speter else 837251881Speter text_mod = (status->text_status != svn_wc_status_normal); 838251881Speter } 839251881Speter 840251881Speter prop_mod = (status->prop_status != svn_wc_status_normal 841251881Speter && status->prop_status != svn_wc_status_none); 842251881Speter 843251881Speter /* Set text/prop modification flags accordingly. */ 844251881Speter if (text_mod) 845251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS; 846251881Speter if (prop_mod) 847251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 848251881Speter } 849251881Speter 850251881Speter /* If the entry has a lock token and it is already a commit candidate, 851251881Speter or the caller wants unmodified locked items to be treated as 852251881Speter such, note this fact. */ 853251881Speter if (status->lock && baton->lock_tokens && (state_flags || just_locked)) 854251881Speter { 855251881Speter state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN; 856251881Speter } 857251881Speter 858251881Speter /* Now, if this is something to commit, add it to our list. */ 859251881Speter if (matches_changelists 860251881Speter && state_flags) 861251881Speter { 862251881Speter /* Finally, add the committable item. */ 863251881Speter SVN_ERR(add_committable(committables, local_abspath, 864251881Speter status->kind, 865251881Speter repos_root_url, 866251881Speter copy_mode 867251881Speter ? commit_relpath 868251881Speter : status->repos_relpath, 869251881Speter copy_mode 870251881Speter ? SVN_INVALID_REVNUM 871251881Speter : node_rev, 872251881Speter cf_relpath, 873251881Speter cf_rev, 874251881Speter moved_from_abspath, 875251881Speter state_flags, 876251881Speter baton->lock_tokens, status->lock, 877251881Speter result_pool, scratch_pool)); 878251881Speter } 879251881Speter 880251881Speter /* Fetch lock tokens for descendants of deleted BASE nodes. */ 881251881Speter if (matches_changelists 882251881Speter && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 883251881Speter && !copy_mode 884251881Speter && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */ 885251881Speter && baton->lock_tokens) 886251881Speter { 887251881Speter apr_hash_t *local_relpath_tokens; 888251881Speter apr_hash_index_t *hi; 889251881Speter 890251881Speter SVN_ERR(svn_wc__node_get_lock_tokens_recursive( 891251881Speter &local_relpath_tokens, wc_ctx, local_abspath, 892251881Speter result_pool, scratch_pool)); 893251881Speter 894251881Speter /* Add tokens to existing hash. */ 895251881Speter for (hi = apr_hash_first(scratch_pool, local_relpath_tokens); 896251881Speter hi; 897251881Speter hi = apr_hash_next(hi)) 898251881Speter { 899251881Speter const void *key; 900251881Speter apr_ssize_t klen; 901251881Speter void * val; 902251881Speter 903251881Speter apr_hash_this(hi, &key, &klen, &val); 904251881Speter 905251881Speter apr_hash_set(baton->lock_tokens, key, klen, val); 906251881Speter } 907251881Speter } 908251881Speter 909251881Speter /* Make sure we check for dangling children on additions 910251881Speter 911251881Speter We perform this operation on the harvest root, and on roots caused by 912251881Speter changelist filtering. 913251881Speter */ 914251881Speter if (matches_changelists 915251881Speter && (is_harvest_root || baton->changelists) 916251881Speter && state_flags 917269847Speter && (is_added || (is_deleted && is_op_root && status->copied)) 918251881Speter && baton->danglers) 919251881Speter { 920251881Speter /* If a node is added, its parent must exist in the repository at the 921251881Speter time of committing */ 922251881Speter apr_hash_t *danglers = baton->danglers; 923251881Speter svn_boolean_t parent_added; 924251881Speter const char *parent_abspath = svn_dirent_dirname(local_abspath, 925251881Speter scratch_pool); 926251881Speter 927251881Speter /* First check if parent is already in the list of commits 928251881Speter (Common case for GUI clients that provide a list of commit targets) */ 929251881Speter if (look_up_committable(committables, parent_abspath, scratch_pool)) 930251881Speter parent_added = FALSE; /* Skip all expensive checks */ 931251881Speter else 932251881Speter SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath, 933251881Speter scratch_pool)); 934251881Speter 935251881Speter if (parent_added) 936251881Speter { 937251881Speter const char *copy_root_abspath; 938251881Speter svn_boolean_t parent_is_copy; 939251881Speter 940251881Speter /* The parent is added, so either it is a copy, or a locally added 941251881Speter * directory. In either case, we require the op-root of the parent 942251881Speter * to be part of the commit. See issue #4059. */ 943251881Speter SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL, 944299742Sdim NULL, NULL, ©_root_abspath, 945251881Speter wc_ctx, parent_abspath, 946251881Speter FALSE, scratch_pool, scratch_pool)); 947251881Speter 948251881Speter if (parent_is_copy) 949251881Speter parent_abspath = copy_root_abspath; 950251881Speter 951251881Speter if (!svn_hash_gets(danglers, parent_abspath)) 952251881Speter { 953251881Speter svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath), 954251881Speter apr_pstrdup(result_pool, local_abspath)); 955251881Speter } 956251881Speter } 957251881Speter } 958251881Speter 959251881Speter if (is_deleted && !is_added) 960251881Speter { 961251881Speter /* Skip all descendants */ 962251881Speter if (status->kind == svn_node_dir) 963251881Speter baton->skip_below_abspath = apr_pstrdup(baton->result_pool, 964251881Speter local_abspath); 965251881Speter return SVN_NO_ERROR; 966251881Speter } 967251881Speter 968251881Speter /* Recursively handle each node according to depth, except when the 969251881Speter node is only being deleted, or is in an added tree (as added trees 970251881Speter use the normal commit handling). */ 971251881Speter if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir) 972251881Speter { 973251881Speter SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables, 974251881Speter repos_root_url, commit_relpath, 975251881Speter baton->check_url_func, 976251881Speter baton->check_url_baton, 977251881Speter result_pool, scratch_pool)); 978251881Speter } 979251881Speter 980251881Speter return SVN_NO_ERROR; 981251881Speter} 982251881Speter 983251881Speter/* Baton for handle_descendants */ 984251881Speterstruct handle_descendants_baton 985251881Speter{ 986251881Speter svn_wc_context_t *wc_ctx; 987251881Speter svn_cancel_func_t cancel_func; 988251881Speter void *cancel_baton; 989251881Speter svn_client__check_url_kind_t check_url_func; 990251881Speter void *check_url_baton; 991269847Speter svn_client__committables_t *committables; 992251881Speter}; 993251881Speter 994251881Speter/* Helper for the commit harvesters */ 995251881Speterstatic svn_error_t * 996251881Speterhandle_descendants(void *baton, 997269847Speter const void *key, apr_ssize_t klen, void *val, 998269847Speter apr_pool_t *pool) 999251881Speter{ 1000251881Speter struct handle_descendants_baton *hdb = baton; 1001251881Speter apr_array_header_t *commit_items = val; 1002251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1003269847Speter const char *repos_root_url = key; 1004251881Speter int i; 1005251881Speter 1006251881Speter for (i = 0; i < commit_items->nelts; i++) 1007251881Speter { 1008251881Speter svn_client_commit_item3_t *item = 1009251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1010251881Speter const apr_array_header_t *absent_descendants; 1011251881Speter int j; 1012251881Speter 1013251881Speter /* Is this a copy operation? */ 1014251881Speter if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1015251881Speter || ! item->copyfrom_url) 1016251881Speter continue; 1017251881Speter 1018251881Speter if (hdb->cancel_func) 1019251881Speter SVN_ERR(hdb->cancel_func(hdb->cancel_baton)); 1020251881Speter 1021251881Speter svn_pool_clear(iterpool); 1022251881Speter 1023251881Speter SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants, 1024251881Speter hdb->wc_ctx, item->path, 1025251881Speter iterpool, iterpool)); 1026251881Speter 1027251881Speter for (j = 0; j < absent_descendants->nelts; j++) 1028251881Speter { 1029251881Speter svn_node_kind_t kind; 1030269847Speter svn_client_commit_item3_t *desc_item; 1031251881Speter const char *relpath = APR_ARRAY_IDX(absent_descendants, j, 1032251881Speter const char *); 1033251881Speter const char *local_abspath = svn_dirent_join(item->path, relpath, 1034251881Speter iterpool); 1035251881Speter 1036269847Speter /* ### Need a sub-iterpool? */ 1037269847Speter 1038269847Speter 1039269847Speter /* We found a 'not present' descendant during a copy (at op_depth>0), 1040269847Speter this is most commonly caused by copying some mixed revision tree. 1041269847Speter 1042269847Speter In this case not present can imply that the node does not exist 1043269847Speter in the parent revision, or that the node does. But we want to copy 1044269847Speter the working copy state in which it does not exist, but might be 1045269847Speter replaced. */ 1046269847Speter 1047269847Speter desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath); 1048269847Speter 1049269847Speter /* If the path has a commit operation (possibly at an higher 1050269847Speter op_depth, we might want to turn an add in a replace. */ 1051269847Speter if (desc_item) 1052251881Speter { 1053269847Speter const char *dir; 1054269847Speter svn_boolean_t found_intermediate = FALSE; 1055251881Speter 1056269847Speter if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1057269847Speter continue; /* We already have a delete or replace */ 1058269847Speter else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1059269847Speter continue; /* Not a copy/add, just a modification */ 1060269847Speter 1061269847Speter dir = svn_dirent_dirname(local_abspath, iterpool); 1062269847Speter 1063269847Speter while (strcmp(dir, item->path)) 1064251881Speter { 1065269847Speter svn_client_commit_item3_t *i_item; 1066269847Speter 1067269847Speter i_item = svn_hash_gets(hdb->committables->by_path, dir); 1068269847Speter 1069269847Speter if (i_item) 1070269847Speter { 1071269847Speter if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1072269847Speter || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1073269847Speter { 1074269847Speter found_intermediate = TRUE; 1075269847Speter break; 1076269847Speter } 1077269847Speter } 1078269847Speter dir = svn_dirent_dirname(dir, iterpool); 1079251881Speter } 1080251881Speter 1081269847Speter if (found_intermediate) 1082269847Speter continue; /* Some intermediate ancestor is an add or delete */ 1083251881Speter 1084269847Speter /* Fall through to detect if we need to turn the add in a 1085269847Speter replace. */ 1086269847Speter } 1087251881Speter 1088251881Speter if (hdb->check_url_func) 1089251881Speter { 1090251881Speter const char *from_url = svn_path_url_add_component2( 1091251881Speter item->copyfrom_url, relpath, 1092251881Speter iterpool); 1093251881Speter 1094251881Speter SVN_ERR(hdb->check_url_func(hdb->check_url_baton, 1095251881Speter &kind, from_url, item->copyfrom_rev, 1096251881Speter iterpool)); 1097251881Speter 1098251881Speter if (kind == svn_node_none) 1099251881Speter continue; /* This node is already deleted */ 1100251881Speter } 1101251881Speter else 1102251881Speter kind = svn_node_unknown; /* 'Ok' for a delete of something */ 1103251881Speter 1104269847Speter if (desc_item) 1105269847Speter { 1106269847Speter /* Extend the existing add/copy item to create a replace */ 1107269847Speter desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 1108269847Speter continue; 1109269847Speter } 1110251881Speter 1111269847Speter /* Add a new commit item that describes the delete */ 1112251881Speter 1113269847Speter SVN_ERR(add_committable(hdb->committables, 1114269847Speter svn_dirent_join(item->path, relpath, 1115269847Speter iterpool), 1116269847Speter kind, 1117269847Speter repos_root_url, 1118269847Speter svn_uri_skip_ancestor( 1119269847Speter repos_root_url, 1120269847Speter svn_path_url_add_component2(item->url, 1121269847Speter relpath, 1122269847Speter iterpool), 1123269847Speter iterpool), 1124269847Speter SVN_INVALID_REVNUM, 1125269847Speter NULL /* copyfrom_relpath */, 1126269847Speter SVN_INVALID_REVNUM, 1127269847Speter NULL /* moved_from_abspath */, 1128269847Speter SVN_CLIENT_COMMIT_ITEM_DELETE, 1129269847Speter NULL /* lock tokens */, 1130269847Speter NULL /* lock */, 1131269847Speter commit_items->pool, 1132269847Speter iterpool)); 1133251881Speter } 1134251881Speter } 1135251881Speter 1136251881Speter svn_pool_destroy(iterpool); 1137251881Speter return SVN_NO_ERROR; 1138251881Speter} 1139251881Speter 1140251881Speter/* Allocate and initialize the COMMITTABLES structure from POOL. 1141251881Speter */ 1142251881Speterstatic void 1143251881Spetercreate_committables(svn_client__committables_t **committables, 1144251881Speter apr_pool_t *pool) 1145251881Speter{ 1146251881Speter *committables = apr_palloc(pool, sizeof(**committables)); 1147251881Speter 1148251881Speter (*committables)->by_repository = apr_hash_make(pool); 1149251881Speter (*committables)->by_path = apr_hash_make(pool); 1150251881Speter} 1151251881Speter 1152251881Spetersvn_error_t * 1153251881Spetersvn_client__harvest_committables(svn_client__committables_t **committables, 1154251881Speter apr_hash_t **lock_tokens, 1155251881Speter const char *base_dir_abspath, 1156251881Speter const apr_array_header_t *targets, 1157251881Speter int depth_empty_start, 1158251881Speter svn_depth_t depth, 1159251881Speter svn_boolean_t just_locked, 1160251881Speter const apr_array_header_t *changelists, 1161251881Speter svn_client__check_url_kind_t check_url_func, 1162251881Speter void *check_url_baton, 1163251881Speter svn_client_ctx_t *ctx, 1164251881Speter apr_pool_t *result_pool, 1165251881Speter apr_pool_t *scratch_pool) 1166251881Speter{ 1167251881Speter int i; 1168251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1169251881Speter apr_hash_t *changelist_hash = NULL; 1170251881Speter struct handle_descendants_baton hdb; 1171251881Speter apr_hash_index_t *hi; 1172251881Speter 1173251881Speter /* It's possible that one of the named targets has a parent that is 1174251881Speter * itself scheduled for addition or replacement -- that is, the 1175251881Speter * parent is not yet versioned in the repository. This is okay, as 1176251881Speter * long as the parent itself is part of this same commit, either 1177251881Speter * directly, or by virtue of a grandparent, great-grandparent, etc, 1178251881Speter * being part of the commit. 1179251881Speter * 1180251881Speter * Since we don't know what's included in the commit until we've 1181251881Speter * harvested all the targets, we can't reliably check this as we 1182251881Speter * go. So in `danglers', we record named targets whose parents 1183251881Speter * do not yet exist in the repository. Then after harvesting the total 1184251881Speter * commit group, we check to make sure those parents are included. 1185251881Speter * 1186251881Speter * Each key of danglers is a parent which does not exist in the 1187251881Speter * repository. The (const char *) value is one of that parent's 1188251881Speter * children which is named as part of the commit; the child is 1189251881Speter * included only to make a better error message. 1190251881Speter * 1191251881Speter * (The reason we don't bother to check unnamed -- i.e, implicit -- 1192251881Speter * targets is that they can only join the commit if their parents 1193251881Speter * did too, so this situation can't arise for them.) 1194251881Speter */ 1195251881Speter apr_hash_t *danglers = apr_hash_make(scratch_pool); 1196251881Speter 1197251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath)); 1198251881Speter 1199251881Speter /* Create the COMMITTABLES structure. */ 1200251881Speter create_committables(committables, result_pool); 1201251881Speter 1202251881Speter /* And the LOCK_TOKENS dito. */ 1203251881Speter *lock_tokens = apr_hash_make(result_pool); 1204251881Speter 1205251881Speter /* If we have a list of changelists, convert that into a hash with 1206251881Speter changelist keys. */ 1207251881Speter if (changelists && changelists->nelts) 1208251881Speter SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, 1209251881Speter scratch_pool)); 1210251881Speter 1211251881Speter for (i = 0; i < targets->nelts; ++i) 1212251881Speter { 1213251881Speter const char *target_abspath; 1214251881Speter 1215251881Speter svn_pool_clear(iterpool); 1216251881Speter 1217251881Speter /* Add the relative portion to the base abspath. */ 1218251881Speter target_abspath = svn_dirent_join(base_dir_abspath, 1219251881Speter APR_ARRAY_IDX(targets, i, const char *), 1220251881Speter iterpool); 1221251881Speter 1222251881Speter /* Handle our TARGET. */ 1223251881Speter /* Make sure this isn't inside a working copy subtree that is 1224251881Speter * marked as tree-conflicted. */ 1225251881Speter SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath, 1226251881Speter ctx->notify_func2, 1227251881Speter ctx->notify_baton2, 1228251881Speter iterpool)); 1229251881Speter 1230251881Speter /* Are the remaining items externals with depth empty? */ 1231251881Speter if (i == depth_empty_start) 1232251881Speter depth = svn_depth_empty; 1233251881Speter 1234251881Speter SVN_ERR(harvest_committables(target_abspath, 1235251881Speter *committables, *lock_tokens, 1236251881Speter NULL /* COPY_MODE_RELPATH */, 1237251881Speter depth, just_locked, changelist_hash, 1238251881Speter danglers, 1239251881Speter check_url_func, check_url_baton, 1240251881Speter ctx->cancel_func, ctx->cancel_baton, 1241251881Speter ctx->notify_func2, ctx->notify_baton2, 1242251881Speter ctx->wc_ctx, result_pool, iterpool)); 1243251881Speter } 1244251881Speter 1245251881Speter hdb.wc_ctx = ctx->wc_ctx; 1246251881Speter hdb.cancel_func = ctx->cancel_func; 1247251881Speter hdb.cancel_baton = ctx->cancel_baton; 1248251881Speter hdb.check_url_func = check_url_func; 1249251881Speter hdb.check_url_baton = check_url_baton; 1250269847Speter hdb.committables = *committables; 1251251881Speter 1252251881Speter SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository, 1253251881Speter handle_descendants, &hdb, iterpool)); 1254251881Speter 1255251881Speter /* Make sure that every path in danglers is part of the commit. */ 1256251881Speter for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi)) 1257251881Speter { 1258299742Sdim const char *dangling_parent = apr_hash_this_key(hi); 1259251881Speter 1260251881Speter svn_pool_clear(iterpool); 1261251881Speter 1262251881Speter if (! look_up_committable(*committables, dangling_parent, iterpool)) 1263251881Speter { 1264299742Sdim const char *dangling_child = apr_hash_this_val(hi); 1265251881Speter 1266251881Speter if (ctx->notify_func2 != NULL) 1267251881Speter { 1268251881Speter svn_wc_notify_t *notify; 1269251881Speter 1270251881Speter notify = svn_wc_create_notify(dangling_child, 1271251881Speter svn_wc_notify_failed_no_parent, 1272251881Speter scratch_pool); 1273251881Speter 1274251881Speter ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1275251881Speter } 1276251881Speter 1277251881Speter return svn_error_createf( 1278251881Speter SVN_ERR_ILLEGAL_TARGET, NULL, 1279251881Speter _("'%s' is not known to exist in the repository " 1280251881Speter "and is not part of the commit, " 1281251881Speter "yet its child '%s' is part of the commit"), 1282251881Speter /* Probably one or both of these is an entry, but 1283251881Speter safest to local_stylize just in case. */ 1284251881Speter svn_dirent_local_style(dangling_parent, iterpool), 1285251881Speter svn_dirent_local_style(dangling_child, iterpool)); 1286251881Speter } 1287251881Speter } 1288251881Speter 1289251881Speter svn_pool_destroy(iterpool); 1290251881Speter 1291251881Speter return SVN_NO_ERROR; 1292251881Speter} 1293251881Speter 1294251881Speterstruct copy_committables_baton 1295251881Speter{ 1296251881Speter svn_client_ctx_t *ctx; 1297251881Speter svn_client__committables_t *committables; 1298251881Speter apr_pool_t *result_pool; 1299251881Speter svn_client__check_url_kind_t check_url_func; 1300251881Speter void *check_url_baton; 1301251881Speter}; 1302251881Speter 1303251881Speterstatic svn_error_t * 1304251881Speterharvest_copy_committables(void *baton, void *item, apr_pool_t *pool) 1305251881Speter{ 1306251881Speter struct copy_committables_baton *btn = baton; 1307251881Speter svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item; 1308251881Speter const char *repos_root_url; 1309251881Speter const char *commit_relpath; 1310251881Speter struct handle_descendants_baton hdb; 1311251881Speter 1312251881Speter /* Read the entry for this SRC. */ 1313251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 1314251881Speter 1315251881Speter SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL, 1316251881Speter btn->ctx->wc_ctx, 1317251881Speter pair->src_abspath_or_url, 1318251881Speter pool, pool)); 1319251881Speter 1320251881Speter commit_relpath = svn_uri_skip_ancestor(repos_root_url, 1321251881Speter pair->dst_abspath_or_url, pool); 1322251881Speter 1323251881Speter /* Handle this SRC. */ 1324251881Speter SVN_ERR(harvest_committables(pair->src_abspath_or_url, 1325251881Speter btn->committables, NULL, 1326251881Speter commit_relpath, 1327251881Speter svn_depth_infinity, 1328251881Speter FALSE, /* JUST_LOCKED */ 1329251881Speter NULL /* changelists */, 1330251881Speter NULL, 1331251881Speter btn->check_url_func, 1332251881Speter btn->check_url_baton, 1333251881Speter btn->ctx->cancel_func, 1334251881Speter btn->ctx->cancel_baton, 1335251881Speter btn->ctx->notify_func2, 1336251881Speter btn->ctx->notify_baton2, 1337251881Speter btn->ctx->wc_ctx, btn->result_pool, pool)); 1338251881Speter 1339251881Speter hdb.wc_ctx = btn->ctx->wc_ctx; 1340251881Speter hdb.cancel_func = btn->ctx->cancel_func; 1341251881Speter hdb.cancel_baton = btn->ctx->cancel_baton; 1342251881Speter hdb.check_url_func = btn->check_url_func; 1343251881Speter hdb.check_url_baton = btn->check_url_baton; 1344269847Speter hdb.committables = btn->committables; 1345251881Speter 1346251881Speter SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository, 1347251881Speter handle_descendants, &hdb, pool)); 1348251881Speter 1349251881Speter return SVN_NO_ERROR; 1350251881Speter} 1351251881Speter 1352251881Speter 1353251881Speter 1354251881Spetersvn_error_t * 1355251881Spetersvn_client__get_copy_committables(svn_client__committables_t **committables, 1356251881Speter const apr_array_header_t *copy_pairs, 1357251881Speter svn_client__check_url_kind_t check_url_func, 1358251881Speter void *check_url_baton, 1359251881Speter svn_client_ctx_t *ctx, 1360251881Speter apr_pool_t *result_pool, 1361251881Speter apr_pool_t *scratch_pool) 1362251881Speter{ 1363251881Speter struct copy_committables_baton btn; 1364251881Speter 1365251881Speter /* Create the COMMITTABLES structure. */ 1366251881Speter create_committables(committables, result_pool); 1367251881Speter 1368251881Speter btn.ctx = ctx; 1369251881Speter btn.committables = *committables; 1370251881Speter btn.result_pool = result_pool; 1371251881Speter 1372251881Speter btn.check_url_func = check_url_func; 1373251881Speter btn.check_url_baton = check_url_baton; 1374251881Speter 1375251881Speter /* For each copy pair, harvest the committables for that pair into the 1376251881Speter committables hash. */ 1377251881Speter return svn_iter_apr_array(NULL, copy_pairs, 1378251881Speter harvest_copy_committables, &btn, scratch_pool); 1379251881Speter} 1380251881Speter 1381251881Speter 1382299742Sdim/* A svn_sort__array()/qsort()-compatible sort routine for sorting 1383299742Sdim an array of svn_client_commit_item_t *'s by their URL member. */ 1384299742Sdimstatic int 1385299742Sdimsort_commit_item_urls(const void *a, const void *b) 1386251881Speter{ 1387251881Speter const svn_client_commit_item3_t *item1 1388251881Speter = *((const svn_client_commit_item3_t * const *) a); 1389251881Speter const svn_client_commit_item3_t *item2 1390251881Speter = *((const svn_client_commit_item3_t * const *) b); 1391251881Speter return svn_path_compare_paths(item1->url, item2->url); 1392251881Speter} 1393251881Speter 1394251881Speter 1395251881Speter 1396251881Spetersvn_error_t * 1397251881Spetersvn_client__condense_commit_items(const char **base_url, 1398251881Speter apr_array_header_t *commit_items, 1399251881Speter apr_pool_t *pool) 1400251881Speter{ 1401251881Speter apr_array_header_t *ci = commit_items; /* convenience */ 1402251881Speter const char *url; 1403251881Speter svn_client_commit_item3_t *item, *last_item = NULL; 1404251881Speter int i; 1405251881Speter 1406251881Speter SVN_ERR_ASSERT(ci && ci->nelts); 1407251881Speter 1408251881Speter /* Sort our commit items by their URLs. */ 1409299742Sdim svn_sort__array(ci, sort_commit_item_urls); 1410251881Speter 1411251881Speter /* Loop through the URLs, finding the longest usable ancestor common 1412251881Speter to all of them, and making sure there are no duplicate URLs. */ 1413251881Speter for (i = 0; i < ci->nelts; i++) 1414251881Speter { 1415251881Speter item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1416251881Speter url = item->url; 1417251881Speter 1418251881Speter if ((last_item) && (strcmp(last_item->url, url) == 0)) 1419251881Speter return svn_error_createf 1420251881Speter (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL, 1421251881Speter _("Cannot commit both '%s' and '%s' as they refer to the same URL"), 1422251881Speter svn_dirent_local_style(item->path, pool), 1423251881Speter svn_dirent_local_style(last_item->path, pool)); 1424251881Speter 1425251881Speter /* In the first iteration, our BASE_URL is just our only 1426251881Speter encountered commit URL to date. After that, we find the 1427251881Speter longest ancestor between the current BASE_URL and the current 1428251881Speter commit URL. */ 1429251881Speter if (i == 0) 1430251881Speter *base_url = apr_pstrdup(pool, url); 1431251881Speter else 1432251881Speter *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool); 1433251881Speter 1434251881Speter /* If our BASE_URL is itself a to-be-committed item, and it is 1435251881Speter anything other than an already-versioned directory with 1436251881Speter property mods, we'll call its parent directory URL the 1437251881Speter BASE_URL. Why? Because we can't have a file URL as our base 1438251881Speter -- period -- and all other directory operations (removal, 1439251881Speter addition, etc.) require that we open that directory's parent 1440251881Speter dir first. */ 1441251881Speter /* ### I don't understand the strlen()s here, hmmm. -kff */ 1442251881Speter if ((strlen(*base_url) == strlen(url)) 1443251881Speter && (! ((item->kind == svn_node_dir) 1444251881Speter && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS))) 1445251881Speter *base_url = svn_uri_dirname(*base_url, pool); 1446251881Speter 1447251881Speter /* Stash our item here for the next iteration. */ 1448251881Speter last_item = item; 1449251881Speter } 1450251881Speter 1451251881Speter /* Now that we've settled on a *BASE_URL, go hack that base off 1452251881Speter of all of our URLs and store it as session_relpath. */ 1453251881Speter for (i = 0; i < ci->nelts; i++) 1454251881Speter { 1455251881Speter svn_client_commit_item3_t *this_item 1456251881Speter = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1457251881Speter 1458251881Speter this_item->session_relpath = svn_uri_skip_ancestor(*base_url, 1459251881Speter this_item->url, pool); 1460251881Speter } 1461251881Speter#ifdef SVN_CLIENT_COMMIT_DEBUG 1462251881Speter /* ### TEMPORARY CODE ### */ 1463251881Speter SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url)); 1464251881Speter SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n")); 1465251881Speter for (i = 0; i < ci->nelts; i++) 1466251881Speter { 1467251881Speter svn_client_commit_item3_t *this_item 1468251881Speter = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1469251881Speter char flags[6]; 1470251881Speter flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1471251881Speter ? 'a' : '-'; 1472251881Speter flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1473251881Speter ? 'd' : '-'; 1474251881Speter flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1475251881Speter ? 't' : '-'; 1476251881Speter flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1477251881Speter ? 'p' : '-'; 1478251881Speter flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1479251881Speter ? 'c' : '-'; 1480251881Speter flags[5] = '\0'; 1481251881Speter SVN_DBG((" %s %6ld '%s' (%s)\n", 1482251881Speter flags, 1483251881Speter this_item->revision, 1484251881Speter this_item->url ? this_item->url : "", 1485251881Speter this_item->copyfrom_url ? this_item->copyfrom_url : "none")); 1486251881Speter } 1487251881Speter#endif /* SVN_CLIENT_COMMIT_DEBUG */ 1488251881Speter 1489251881Speter return SVN_NO_ERROR; 1490251881Speter} 1491251881Speter 1492251881Speter 1493251881Speterstruct file_mod_t 1494251881Speter{ 1495251881Speter const svn_client_commit_item3_t *item; 1496251881Speter void *file_baton; 1497299742Sdim apr_pool_t *file_pool; 1498251881Speter}; 1499251881Speter 1500251881Speter 1501251881Speter/* A baton for use while driving a path-based editor driver for commit */ 1502251881Speterstruct item_commit_baton 1503251881Speter{ 1504251881Speter const svn_delta_editor_t *editor; /* commit editor */ 1505251881Speter void *edit_baton; /* commit editor's baton */ 1506251881Speter apr_hash_t *file_mods; /* hash: path->file_mod_t */ 1507251881Speter const char *notify_path_prefix; /* notification path prefix 1508251881Speter (NULL is okay, else abs path) */ 1509251881Speter svn_client_ctx_t *ctx; /* client context baton */ 1510251881Speter apr_hash_t *commit_items; /* the committables */ 1511251881Speter const char *base_url; /* The session url for the commit */ 1512251881Speter}; 1513251881Speter 1514251881Speter 1515251881Speter/* Drive CALLBACK_BATON->editor with the change described by the item in 1516251881Speter * CALLBACK_BATON->commit_items that is keyed by PATH. If the change 1517251881Speter * includes a text mod, however, call the editor's file_open() function 1518251881Speter * but do not send the text mod to the editor; instead, add a mapping of 1519251881Speter * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods. 1520251881Speter * 1521251881Speter * Before driving the editor, call the cancellation and notification 1522251881Speter * callbacks in CALLBACK_BATON->ctx, if present. 1523251881Speter * 1524251881Speter * This implements svn_delta_path_driver_cb_func_t. */ 1525251881Speterstatic svn_error_t * 1526251881Speterdo_item_commit(void **dir_baton, 1527251881Speter void *parent_baton, 1528251881Speter void *callback_baton, 1529251881Speter const char *path, 1530251881Speter apr_pool_t *pool) 1531251881Speter{ 1532251881Speter struct item_commit_baton *icb = callback_baton; 1533251881Speter const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items, 1534251881Speter path); 1535251881Speter svn_node_kind_t kind = item->kind; 1536251881Speter void *file_baton = NULL; 1537251881Speter apr_pool_t *file_pool = NULL; 1538251881Speter const svn_delta_editor_t *editor = icb->editor; 1539251881Speter apr_hash_t *file_mods = icb->file_mods; 1540251881Speter svn_client_ctx_t *ctx = icb->ctx; 1541251881Speter svn_error_t *err; 1542251881Speter const char *local_abspath = NULL; 1543251881Speter 1544251881Speter /* Do some initializations. */ 1545251881Speter *dir_baton = NULL; 1546251881Speter if (item->kind != svn_node_none && item->path) 1547251881Speter { 1548251881Speter /* We always get an absolute path, see svn_client_commit_item3_t. */ 1549251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); 1550251881Speter local_abspath = item->path; 1551251881Speter } 1552251881Speter 1553251881Speter /* If this is a file with textual mods, we'll be keeping its baton 1554251881Speter around until the end of the commit. So just lump its memory into 1555251881Speter a single, big, all-the-file-batons-in-here pool. Otherwise, we 1556251881Speter can just use POOL, and trust our caller to clean that mess up. */ 1557251881Speter if ((kind == svn_node_file) 1558251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1559251881Speter file_pool = apr_hash_pool_get(file_mods); 1560251881Speter else 1561251881Speter file_pool = pool; 1562251881Speter 1563299742Sdim /* Subpools are cheap, but memory isn't */ 1564299742Sdim file_pool = svn_pool_create(file_pool); 1565299742Sdim 1566251881Speter /* Call the cancellation function. */ 1567251881Speter if (ctx->cancel_func) 1568251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1569251881Speter 1570251881Speter /* Validation. */ 1571251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1572251881Speter { 1573251881Speter if (! item->copyfrom_url) 1574251881Speter return svn_error_createf 1575251881Speter (SVN_ERR_BAD_URL, NULL, 1576251881Speter _("Commit item '%s' has copy flag but no copyfrom URL"), 1577251881Speter svn_dirent_local_style(path, pool)); 1578251881Speter if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev)) 1579251881Speter return svn_error_createf 1580251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, 1581251881Speter _("Commit item '%s' has copy flag but an invalid revision"), 1582251881Speter svn_dirent_local_style(path, pool)); 1583251881Speter } 1584251881Speter 1585251881Speter /* If a feedback table was supplied by the application layer, 1586251881Speter describe what we're about to do to this item. */ 1587251881Speter if (ctx->notify_func2 && item->path) 1588251881Speter { 1589251881Speter const char *npath = item->path; 1590251881Speter svn_wc_notify_t *notify; 1591251881Speter 1592251881Speter if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1593251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1594251881Speter { 1595251881Speter /* We don't print the "(bin)" notice for binary files when 1596251881Speter replacing, only when adding. So we don't bother to get 1597251881Speter the mime-type here. */ 1598251881Speter if (item->copyfrom_url) 1599251881Speter notify = svn_wc_create_notify(npath, 1600251881Speter svn_wc_notify_commit_copied_replaced, 1601251881Speter pool); 1602251881Speter else 1603251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced, 1604251881Speter pool); 1605251881Speter 1606251881Speter } 1607251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1608251881Speter { 1609251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted, 1610251881Speter pool); 1611251881Speter } 1612251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1613251881Speter { 1614251881Speter if (item->copyfrom_url) 1615251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied, 1616251881Speter pool); 1617251881Speter else 1618251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added, 1619251881Speter pool); 1620251881Speter 1621251881Speter if (item->kind == svn_node_file) 1622251881Speter { 1623251881Speter const svn_string_t *propval; 1624251881Speter 1625251881Speter SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath, 1626251881Speter SVN_PROP_MIME_TYPE, pool, pool)); 1627251881Speter 1628251881Speter if (propval) 1629251881Speter notify->mime_type = propval->data; 1630251881Speter } 1631251881Speter } 1632251881Speter else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1633251881Speter || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)) 1634251881Speter { 1635251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified, 1636251881Speter pool); 1637251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1638251881Speter notify->content_state = svn_wc_notify_state_changed; 1639251881Speter else 1640251881Speter notify->content_state = svn_wc_notify_state_unchanged; 1641251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1642251881Speter notify->prop_state = svn_wc_notify_state_changed; 1643251881Speter else 1644251881Speter notify->prop_state = svn_wc_notify_state_unchanged; 1645251881Speter } 1646251881Speter else 1647251881Speter notify = NULL; 1648251881Speter 1649299742Sdim 1650251881Speter if (notify) 1651251881Speter { 1652251881Speter notify->kind = item->kind; 1653251881Speter notify->path_prefix = icb->notify_path_prefix; 1654299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, pool); 1655251881Speter } 1656251881Speter } 1657251881Speter 1658251881Speter /* If this item is supposed to be deleted, do so. */ 1659251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1660251881Speter { 1661251881Speter SVN_ERR_ASSERT(parent_baton); 1662251881Speter err = editor->delete_entry(path, item->revision, 1663251881Speter parent_baton, pool); 1664251881Speter 1665251881Speter if (err) 1666251881Speter goto fixup_error; 1667251881Speter } 1668251881Speter 1669251881Speter /* If this item is supposed to be added, do so. */ 1670251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1671251881Speter { 1672251881Speter if (kind == svn_node_file) 1673251881Speter { 1674251881Speter SVN_ERR_ASSERT(parent_baton); 1675251881Speter err = editor->add_file( 1676251881Speter path, parent_baton, item->copyfrom_url, 1677251881Speter item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1678251881Speter file_pool, &file_baton); 1679251881Speter } 1680251881Speter else /* May be svn_node_none when adding parent dirs for a copy. */ 1681251881Speter { 1682251881Speter SVN_ERR_ASSERT(parent_baton); 1683251881Speter err = editor->add_directory( 1684251881Speter path, parent_baton, item->copyfrom_url, 1685251881Speter item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1686251881Speter pool, dir_baton); 1687251881Speter } 1688251881Speter 1689251881Speter if (err) 1690251881Speter goto fixup_error; 1691251881Speter 1692251881Speter /* Set other prop-changes, if available in the baton */ 1693251881Speter if (item->outgoing_prop_changes) 1694251881Speter { 1695251881Speter svn_prop_t *prop; 1696251881Speter apr_array_header_t *prop_changes = item->outgoing_prop_changes; 1697251881Speter int ctr; 1698251881Speter for (ctr = 0; ctr < prop_changes->nelts; ctr++) 1699251881Speter { 1700251881Speter prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *); 1701251881Speter if (kind == svn_node_file) 1702251881Speter { 1703251881Speter err = editor->change_file_prop(file_baton, prop->name, 1704251881Speter prop->value, pool); 1705251881Speter } 1706251881Speter else 1707251881Speter { 1708251881Speter err = editor->change_dir_prop(*dir_baton, prop->name, 1709251881Speter prop->value, pool); 1710251881Speter } 1711251881Speter 1712251881Speter if (err) 1713251881Speter goto fixup_error; 1714251881Speter } 1715251881Speter } 1716251881Speter } 1717251881Speter 1718251881Speter /* Now handle property mods. */ 1719251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1720251881Speter { 1721251881Speter if (kind == svn_node_file) 1722251881Speter { 1723251881Speter if (! file_baton) 1724251881Speter { 1725251881Speter SVN_ERR_ASSERT(parent_baton); 1726251881Speter err = editor->open_file(path, parent_baton, 1727251881Speter item->revision, 1728251881Speter file_pool, &file_baton); 1729251881Speter 1730251881Speter if (err) 1731251881Speter goto fixup_error; 1732251881Speter } 1733251881Speter } 1734251881Speter else 1735251881Speter { 1736251881Speter if (! *dir_baton) 1737251881Speter { 1738251881Speter if (! parent_baton) 1739251881Speter { 1740251881Speter err = editor->open_root(icb->edit_baton, item->revision, 1741251881Speter pool, dir_baton); 1742251881Speter } 1743251881Speter else 1744251881Speter { 1745251881Speter err = editor->open_directory(path, parent_baton, 1746251881Speter item->revision, 1747251881Speter pool, dir_baton); 1748251881Speter } 1749251881Speter 1750251881Speter if (err) 1751251881Speter goto fixup_error; 1752251881Speter } 1753251881Speter } 1754251881Speter 1755251881Speter /* When committing a directory that no longer exists in the 1756251881Speter repository, a "not found" error does not occur immediately 1757251881Speter upon opening the directory. It appears here during the delta 1758251881Speter transmisssion. */ 1759251881Speter err = svn_wc_transmit_prop_deltas2( 1760251881Speter ctx->wc_ctx, local_abspath, editor, 1761251881Speter (kind == svn_node_dir) ? *dir_baton : file_baton, pool); 1762251881Speter 1763251881Speter if (err) 1764251881Speter goto fixup_error; 1765251881Speter 1766251881Speter /* Make any additional client -> repository prop changes. */ 1767251881Speter if (item->outgoing_prop_changes) 1768251881Speter { 1769251881Speter svn_prop_t *prop; 1770251881Speter int i; 1771251881Speter 1772251881Speter for (i = 0; i < item->outgoing_prop_changes->nelts; i++) 1773251881Speter { 1774251881Speter prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i, 1775251881Speter svn_prop_t *); 1776251881Speter if (kind == svn_node_file) 1777251881Speter { 1778251881Speter err = editor->change_file_prop(file_baton, prop->name, 1779251881Speter prop->value, pool); 1780251881Speter } 1781251881Speter else 1782251881Speter { 1783251881Speter err = editor->change_dir_prop(*dir_baton, prop->name, 1784251881Speter prop->value, pool); 1785251881Speter } 1786251881Speter 1787251881Speter if (err) 1788251881Speter goto fixup_error; 1789251881Speter } 1790251881Speter } 1791251881Speter } 1792251881Speter 1793251881Speter /* Finally, handle text mods (in that we need to open a file if it 1794251881Speter hasn't already been opened, and we need to put the file baton in 1795251881Speter our FILES hash). */ 1796251881Speter if ((kind == svn_node_file) 1797251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1798251881Speter { 1799251881Speter struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod)); 1800251881Speter 1801251881Speter if (! file_baton) 1802251881Speter { 1803251881Speter SVN_ERR_ASSERT(parent_baton); 1804251881Speter err = editor->open_file(path, parent_baton, 1805251881Speter item->revision, 1806251881Speter file_pool, &file_baton); 1807251881Speter 1808251881Speter if (err) 1809251881Speter goto fixup_error; 1810251881Speter } 1811251881Speter 1812251881Speter /* Add this file mod to the FILE_MODS hash. */ 1813251881Speter mod->item = item; 1814251881Speter mod->file_baton = file_baton; 1815299742Sdim mod->file_pool = file_pool; 1816251881Speter svn_hash_sets(file_mods, item->session_relpath, mod); 1817251881Speter } 1818251881Speter else if (file_baton) 1819251881Speter { 1820251881Speter /* Close any outstanding file batons that didn't get caught by 1821251881Speter the "has local mods" conditional above. */ 1822251881Speter err = editor->close_file(file_baton, NULL, file_pool); 1823299742Sdim svn_pool_destroy(file_pool); 1824251881Speter if (err) 1825251881Speter goto fixup_error; 1826251881Speter } 1827251881Speter 1828251881Speter return SVN_NO_ERROR; 1829251881Speter 1830251881Speterfixup_error: 1831251881Speter return svn_error_trace(fixup_commit_error(local_abspath, 1832251881Speter icb->base_url, 1833251881Speter path, kind, 1834251881Speter err, ctx, pool)); 1835251881Speter} 1836251881Speter 1837251881Spetersvn_error_t * 1838251881Spetersvn_client__do_commit(const char *base_url, 1839251881Speter const apr_array_header_t *commit_items, 1840251881Speter const svn_delta_editor_t *editor, 1841251881Speter void *edit_baton, 1842251881Speter const char *notify_path_prefix, 1843251881Speter apr_hash_t **sha1_checksums, 1844251881Speter svn_client_ctx_t *ctx, 1845251881Speter apr_pool_t *result_pool, 1846251881Speter apr_pool_t *scratch_pool) 1847251881Speter{ 1848251881Speter apr_hash_t *file_mods = apr_hash_make(scratch_pool); 1849251881Speter apr_hash_t *items_hash = apr_hash_make(scratch_pool); 1850251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1851251881Speter apr_hash_index_t *hi; 1852251881Speter int i; 1853251881Speter struct item_commit_baton cb_baton; 1854251881Speter apr_array_header_t *paths = 1855251881Speter apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *)); 1856251881Speter 1857251881Speter /* Ditto for the checksums. */ 1858251881Speter if (sha1_checksums) 1859251881Speter *sha1_checksums = apr_hash_make(result_pool); 1860251881Speter 1861251881Speter /* Build a hash from our COMMIT_ITEMS array, keyed on the 1862251881Speter relative paths (which come from the item URLs). And 1863251881Speter keep an array of those decoded paths, too. */ 1864251881Speter for (i = 0; i < commit_items->nelts; i++) 1865251881Speter { 1866251881Speter svn_client_commit_item3_t *item = 1867251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1868251881Speter const char *path = item->session_relpath; 1869251881Speter svn_hash_sets(items_hash, path, item); 1870251881Speter APR_ARRAY_PUSH(paths, const char *) = path; 1871251881Speter } 1872251881Speter 1873251881Speter /* Setup the callback baton. */ 1874251881Speter cb_baton.editor = editor; 1875251881Speter cb_baton.edit_baton = edit_baton; 1876251881Speter cb_baton.file_mods = file_mods; 1877251881Speter cb_baton.notify_path_prefix = notify_path_prefix; 1878251881Speter cb_baton.ctx = ctx; 1879251881Speter cb_baton.commit_items = items_hash; 1880251881Speter cb_baton.base_url = base_url; 1881251881Speter 1882251881Speter /* Drive the commit editor! */ 1883251881Speter SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1884251881Speter do_item_commit, &cb_baton, scratch_pool)); 1885251881Speter 1886251881Speter /* Transmit outstanding text deltas. */ 1887251881Speter for (hi = apr_hash_first(scratch_pool, file_mods); 1888251881Speter hi; 1889251881Speter hi = apr_hash_next(hi)) 1890251881Speter { 1891299742Sdim struct file_mod_t *mod = apr_hash_this_val(hi); 1892251881Speter const svn_client_commit_item3_t *item = mod->item; 1893251881Speter const svn_checksum_t *new_text_base_md5_checksum; 1894251881Speter const svn_checksum_t *new_text_base_sha1_checksum; 1895251881Speter svn_boolean_t fulltext = FALSE; 1896251881Speter svn_error_t *err; 1897251881Speter 1898251881Speter svn_pool_clear(iterpool); 1899251881Speter 1900251881Speter /* Transmit the entry. */ 1901251881Speter if (ctx->cancel_func) 1902251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1903251881Speter 1904251881Speter if (ctx->notify_func2) 1905251881Speter { 1906251881Speter svn_wc_notify_t *notify; 1907251881Speter notify = svn_wc_create_notify(item->path, 1908251881Speter svn_wc_notify_commit_postfix_txdelta, 1909251881Speter iterpool); 1910251881Speter notify->kind = svn_node_file; 1911251881Speter notify->path_prefix = notify_path_prefix; 1912251881Speter ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1913251881Speter } 1914251881Speter 1915251881Speter /* If the node has no history, transmit full text */ 1916251881Speter if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1917251881Speter && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 1918251881Speter fulltext = TRUE; 1919251881Speter 1920251881Speter err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum, 1921251881Speter &new_text_base_sha1_checksum, 1922251881Speter ctx->wc_ctx, item->path, 1923251881Speter fulltext, editor, mod->file_baton, 1924251881Speter result_pool, iterpool); 1925251881Speter 1926251881Speter if (err) 1927251881Speter { 1928251881Speter svn_pool_destroy(iterpool); /* Close tempfiles */ 1929251881Speter return svn_error_trace(fixup_commit_error(item->path, 1930251881Speter base_url, 1931251881Speter item->session_relpath, 1932251881Speter svn_node_file, 1933251881Speter err, ctx, scratch_pool)); 1934251881Speter } 1935251881Speter 1936251881Speter if (sha1_checksums) 1937251881Speter svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum); 1938299742Sdim 1939299742Sdim svn_pool_destroy(mod->file_pool); 1940251881Speter } 1941251881Speter 1942299742Sdim if (ctx->notify_func2) 1943299742Sdim { 1944299742Sdim svn_wc_notify_t *notify; 1945299742Sdim notify = svn_wc_create_notify_url(base_url, 1946299742Sdim svn_wc_notify_commit_finalizing, 1947299742Sdim iterpool); 1948299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1949299742Sdim } 1950299742Sdim 1951251881Speter svn_pool_destroy(iterpool); 1952251881Speter 1953251881Speter /* Close the edit. */ 1954251881Speter return svn_error_trace(editor->close_edit(edit_baton, scratch_pool)); 1955251881Speter} 1956251881Speter 1957251881Speter 1958251881Spetersvn_error_t * 1959251881Spetersvn_client__get_log_msg(const char **log_msg, 1960251881Speter const char **tmp_file, 1961251881Speter const apr_array_header_t *commit_items, 1962251881Speter svn_client_ctx_t *ctx, 1963251881Speter apr_pool_t *pool) 1964251881Speter{ 1965251881Speter if (ctx->log_msg_func3) 1966251881Speter { 1967251881Speter /* The client provided a callback function for the current API. 1968251881Speter Forward the call to it directly. */ 1969251881Speter return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items, 1970251881Speter ctx->log_msg_baton3, pool); 1971251881Speter } 1972251881Speter else if (ctx->log_msg_func2 || ctx->log_msg_func) 1973251881Speter { 1974251881Speter /* The client provided a pre-1.5 (or pre-1.3) API callback 1975251881Speter function. Convert the commit_items list to the appropriate 1976251881Speter type, and forward call to it. */ 1977251881Speter svn_error_t *err; 1978251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 1979251881Speter apr_array_header_t *old_commit_items = 1980251881Speter apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*)); 1981251881Speter 1982251881Speter int i; 1983251881Speter for (i = 0; i < commit_items->nelts; i++) 1984251881Speter { 1985251881Speter svn_client_commit_item3_t *item = 1986251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1987251881Speter 1988251881Speter if (ctx->log_msg_func2) 1989251881Speter { 1990251881Speter svn_client_commit_item2_t *old_item = 1991251881Speter apr_pcalloc(scratch_pool, sizeof(*old_item)); 1992251881Speter 1993251881Speter old_item->path = item->path; 1994251881Speter old_item->kind = item->kind; 1995251881Speter old_item->url = item->url; 1996251881Speter old_item->revision = item->revision; 1997251881Speter old_item->copyfrom_url = item->copyfrom_url; 1998251881Speter old_item->copyfrom_rev = item->copyfrom_rev; 1999251881Speter old_item->state_flags = item->state_flags; 2000251881Speter old_item->wcprop_changes = item->incoming_prop_changes; 2001251881Speter 2002251881Speter APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) = 2003251881Speter old_item; 2004251881Speter } 2005251881Speter else /* ctx->log_msg_func */ 2006251881Speter { 2007251881Speter svn_client_commit_item_t *old_item = 2008251881Speter apr_pcalloc(scratch_pool, sizeof(*old_item)); 2009251881Speter 2010251881Speter old_item->path = item->path; 2011251881Speter old_item->kind = item->kind; 2012251881Speter old_item->url = item->url; 2013251881Speter /* The pre-1.3 API used the revision field for copyfrom_rev 2014251881Speter and revision depeding of copyfrom_url. */ 2015251881Speter old_item->revision = item->copyfrom_url ? 2016251881Speter item->copyfrom_rev : item->revision; 2017251881Speter old_item->copyfrom_url = item->copyfrom_url; 2018251881Speter old_item->state_flags = item->state_flags; 2019251881Speter old_item->wcprop_changes = item->incoming_prop_changes; 2020251881Speter 2021251881Speter APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) = 2022251881Speter old_item; 2023251881Speter } 2024251881Speter } 2025251881Speter 2026251881Speter if (ctx->log_msg_func2) 2027251881Speter err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items, 2028251881Speter ctx->log_msg_baton2, pool); 2029251881Speter else 2030251881Speter err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items, 2031251881Speter ctx->log_msg_baton, pool); 2032251881Speter svn_pool_destroy(scratch_pool); 2033251881Speter return err; 2034251881Speter } 2035251881Speter else 2036251881Speter { 2037251881Speter /* No log message callback was provided by the client. */ 2038251881Speter *log_msg = ""; 2039251881Speter *tmp_file = NULL; 2040251881Speter return SVN_NO_ERROR; 2041251881Speter } 2042251881Speter} 2043251881Speter 2044251881Spetersvn_error_t * 2045251881Spetersvn_client__ensure_revprop_table(apr_hash_t **revprop_table_out, 2046251881Speter const apr_hash_t *revprop_table_in, 2047251881Speter const char *log_msg, 2048251881Speter svn_client_ctx_t *ctx, 2049251881Speter apr_pool_t *pool) 2050251881Speter{ 2051251881Speter apr_hash_t *new_revprop_table; 2052251881Speter if (revprop_table_in) 2053251881Speter { 2054251881Speter if (svn_prop_has_svn_prop(revprop_table_in, pool)) 2055251881Speter return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 2056251881Speter _("Standard properties can't be set " 2057251881Speter "explicitly as revision properties")); 2058251881Speter new_revprop_table = apr_hash_copy(pool, revprop_table_in); 2059251881Speter } 2060251881Speter else 2061251881Speter { 2062251881Speter new_revprop_table = apr_hash_make(pool); 2063251881Speter } 2064251881Speter svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG, 2065251881Speter svn_string_create(log_msg, pool)); 2066251881Speter *revprop_table_out = new_revprop_table; 2067251881Speter return SVN_NO_ERROR; 2068251881Speter} 2069