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