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 895269847Speter && (is_added || (is_deleted && is_op_root && status->copied)) 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; 969269847Speter svn_client__committables_t *committables; 970251881Speter}; 971251881Speter 972251881Speter/* Helper for the commit harvesters */ 973251881Speterstatic svn_error_t * 974251881Speterhandle_descendants(void *baton, 975269847Speter const void *key, apr_ssize_t klen, void *val, 976269847Speter apr_pool_t *pool) 977251881Speter{ 978251881Speter struct handle_descendants_baton *hdb = baton; 979251881Speter apr_array_header_t *commit_items = val; 980251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 981269847Speter const char *repos_root_url = key; 982251881Speter int i; 983251881Speter 984251881Speter for (i = 0; i < commit_items->nelts; i++) 985251881Speter { 986251881Speter svn_client_commit_item3_t *item = 987251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 988251881Speter const apr_array_header_t *absent_descendants; 989251881Speter int j; 990251881Speter 991251881Speter /* Is this a copy operation? */ 992251881Speter if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 993251881Speter || ! item->copyfrom_url) 994251881Speter continue; 995251881Speter 996251881Speter if (hdb->cancel_func) 997251881Speter SVN_ERR(hdb->cancel_func(hdb->cancel_baton)); 998251881Speter 999251881Speter svn_pool_clear(iterpool); 1000251881Speter 1001251881Speter SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants, 1002251881Speter hdb->wc_ctx, item->path, 1003251881Speter iterpool, iterpool)); 1004251881Speter 1005251881Speter for (j = 0; j < absent_descendants->nelts; j++) 1006251881Speter { 1007251881Speter svn_node_kind_t kind; 1008269847Speter svn_client_commit_item3_t *desc_item; 1009251881Speter const char *relpath = APR_ARRAY_IDX(absent_descendants, j, 1010251881Speter const char *); 1011251881Speter const char *local_abspath = svn_dirent_join(item->path, relpath, 1012251881Speter iterpool); 1013251881Speter 1014269847Speter /* ### Need a sub-iterpool? */ 1015269847Speter 1016269847Speter 1017269847Speter /* We found a 'not present' descendant during a copy (at op_depth>0), 1018269847Speter this is most commonly caused by copying some mixed revision tree. 1019269847Speter 1020269847Speter In this case not present can imply that the node does not exist 1021269847Speter in the parent revision, or that the node does. But we want to copy 1022269847Speter the working copy state in which it does not exist, but might be 1023269847Speter replaced. */ 1024269847Speter 1025269847Speter desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath); 1026269847Speter 1027269847Speter /* If the path has a commit operation (possibly at an higher 1028269847Speter op_depth, we might want to turn an add in a replace. */ 1029269847Speter if (desc_item) 1030251881Speter { 1031269847Speter const char *dir; 1032269847Speter svn_boolean_t found_intermediate = FALSE; 1033251881Speter 1034269847Speter if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1035269847Speter continue; /* We already have a delete or replace */ 1036269847Speter else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1037269847Speter continue; /* Not a copy/add, just a modification */ 1038269847Speter 1039269847Speter dir = svn_dirent_dirname(local_abspath, iterpool); 1040269847Speter 1041269847Speter while (strcmp(dir, item->path)) 1042251881Speter { 1043269847Speter svn_client_commit_item3_t *i_item; 1044269847Speter 1045269847Speter i_item = svn_hash_gets(hdb->committables->by_path, dir); 1046269847Speter 1047269847Speter if (i_item) 1048269847Speter { 1049269847Speter if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1050269847Speter || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1051269847Speter { 1052269847Speter found_intermediate = TRUE; 1053269847Speter break; 1054269847Speter } 1055269847Speter } 1056269847Speter dir = svn_dirent_dirname(dir, iterpool); 1057251881Speter } 1058251881Speter 1059269847Speter if (found_intermediate) 1060269847Speter continue; /* Some intermediate ancestor is an add or delete */ 1061251881Speter 1062269847Speter /* Fall through to detect if we need to turn the add in a 1063269847Speter replace. */ 1064269847Speter } 1065251881Speter 1066251881Speter if (hdb->check_url_func) 1067251881Speter { 1068251881Speter const char *from_url = svn_path_url_add_component2( 1069251881Speter item->copyfrom_url, relpath, 1070251881Speter iterpool); 1071251881Speter 1072251881Speter SVN_ERR(hdb->check_url_func(hdb->check_url_baton, 1073251881Speter &kind, from_url, item->copyfrom_rev, 1074251881Speter iterpool)); 1075251881Speter 1076251881Speter if (kind == svn_node_none) 1077251881Speter continue; /* This node is already deleted */ 1078251881Speter } 1079251881Speter else 1080251881Speter kind = svn_node_unknown; /* 'Ok' for a delete of something */ 1081251881Speter 1082269847Speter if (desc_item) 1083269847Speter { 1084269847Speter /* Extend the existing add/copy item to create a replace */ 1085269847Speter desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; 1086269847Speter continue; 1087269847Speter } 1088251881Speter 1089269847Speter /* Add a new commit item that describes the delete */ 1090251881Speter 1091269847Speter SVN_ERR(add_committable(hdb->committables, 1092269847Speter svn_dirent_join(item->path, relpath, 1093269847Speter iterpool), 1094269847Speter kind, 1095269847Speter repos_root_url, 1096269847Speter svn_uri_skip_ancestor( 1097269847Speter repos_root_url, 1098269847Speter svn_path_url_add_component2(item->url, 1099269847Speter relpath, 1100269847Speter iterpool), 1101269847Speter iterpool), 1102269847Speter SVN_INVALID_REVNUM, 1103269847Speter NULL /* copyfrom_relpath */, 1104269847Speter SVN_INVALID_REVNUM, 1105269847Speter NULL /* moved_from_abspath */, 1106269847Speter SVN_CLIENT_COMMIT_ITEM_DELETE, 1107269847Speter NULL /* lock tokens */, 1108269847Speter NULL /* lock */, 1109269847Speter commit_items->pool, 1110269847Speter iterpool)); 1111251881Speter } 1112251881Speter } 1113251881Speter 1114251881Speter svn_pool_destroy(iterpool); 1115251881Speter return SVN_NO_ERROR; 1116251881Speter} 1117251881Speter 1118251881Speter/* Allocate and initialize the COMMITTABLES structure from POOL. 1119251881Speter */ 1120251881Speterstatic void 1121251881Spetercreate_committables(svn_client__committables_t **committables, 1122251881Speter apr_pool_t *pool) 1123251881Speter{ 1124251881Speter *committables = apr_palloc(pool, sizeof(**committables)); 1125251881Speter 1126251881Speter (*committables)->by_repository = apr_hash_make(pool); 1127251881Speter (*committables)->by_path = apr_hash_make(pool); 1128251881Speter} 1129251881Speter 1130251881Spetersvn_error_t * 1131251881Spetersvn_client__harvest_committables(svn_client__committables_t **committables, 1132251881Speter apr_hash_t **lock_tokens, 1133251881Speter const char *base_dir_abspath, 1134251881Speter const apr_array_header_t *targets, 1135251881Speter int depth_empty_start, 1136251881Speter svn_depth_t depth, 1137251881Speter svn_boolean_t just_locked, 1138251881Speter const apr_array_header_t *changelists, 1139251881Speter svn_client__check_url_kind_t check_url_func, 1140251881Speter void *check_url_baton, 1141251881Speter svn_client_ctx_t *ctx, 1142251881Speter apr_pool_t *result_pool, 1143251881Speter apr_pool_t *scratch_pool) 1144251881Speter{ 1145251881Speter int i; 1146251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1147251881Speter apr_hash_t *changelist_hash = NULL; 1148251881Speter struct handle_descendants_baton hdb; 1149251881Speter apr_hash_index_t *hi; 1150251881Speter 1151251881Speter /* It's possible that one of the named targets has a parent that is 1152251881Speter * itself scheduled for addition or replacement -- that is, the 1153251881Speter * parent is not yet versioned in the repository. This is okay, as 1154251881Speter * long as the parent itself is part of this same commit, either 1155251881Speter * directly, or by virtue of a grandparent, great-grandparent, etc, 1156251881Speter * being part of the commit. 1157251881Speter * 1158251881Speter * Since we don't know what's included in the commit until we've 1159251881Speter * harvested all the targets, we can't reliably check this as we 1160251881Speter * go. So in `danglers', we record named targets whose parents 1161251881Speter * do not yet exist in the repository. Then after harvesting the total 1162251881Speter * commit group, we check to make sure those parents are included. 1163251881Speter * 1164251881Speter * Each key of danglers is a parent which does not exist in the 1165251881Speter * repository. The (const char *) value is one of that parent's 1166251881Speter * children which is named as part of the commit; the child is 1167251881Speter * included only to make a better error message. 1168251881Speter * 1169251881Speter * (The reason we don't bother to check unnamed -- i.e, implicit -- 1170251881Speter * targets is that they can only join the commit if their parents 1171251881Speter * did too, so this situation can't arise for them.) 1172251881Speter */ 1173251881Speter apr_hash_t *danglers = apr_hash_make(scratch_pool); 1174251881Speter 1175251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath)); 1176251881Speter 1177251881Speter /* Create the COMMITTABLES structure. */ 1178251881Speter create_committables(committables, result_pool); 1179251881Speter 1180251881Speter /* And the LOCK_TOKENS dito. */ 1181251881Speter *lock_tokens = apr_hash_make(result_pool); 1182251881Speter 1183251881Speter /* If we have a list of changelists, convert that into a hash with 1184251881Speter changelist keys. */ 1185251881Speter if (changelists && changelists->nelts) 1186251881Speter SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, 1187251881Speter scratch_pool)); 1188251881Speter 1189251881Speter for (i = 0; i < targets->nelts; ++i) 1190251881Speter { 1191251881Speter const char *target_abspath; 1192251881Speter 1193251881Speter svn_pool_clear(iterpool); 1194251881Speter 1195251881Speter /* Add the relative portion to the base abspath. */ 1196251881Speter target_abspath = svn_dirent_join(base_dir_abspath, 1197251881Speter APR_ARRAY_IDX(targets, i, const char *), 1198251881Speter iterpool); 1199251881Speter 1200251881Speter /* Handle our TARGET. */ 1201251881Speter /* Make sure this isn't inside a working copy subtree that is 1202251881Speter * marked as tree-conflicted. */ 1203251881Speter SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath, 1204251881Speter ctx->notify_func2, 1205251881Speter ctx->notify_baton2, 1206251881Speter iterpool)); 1207251881Speter 1208251881Speter /* Are the remaining items externals with depth empty? */ 1209251881Speter if (i == depth_empty_start) 1210251881Speter depth = svn_depth_empty; 1211251881Speter 1212251881Speter SVN_ERR(harvest_committables(target_abspath, 1213251881Speter *committables, *lock_tokens, 1214251881Speter NULL /* COPY_MODE_RELPATH */, 1215251881Speter depth, just_locked, changelist_hash, 1216251881Speter danglers, 1217251881Speter check_url_func, check_url_baton, 1218251881Speter ctx->cancel_func, ctx->cancel_baton, 1219251881Speter ctx->notify_func2, ctx->notify_baton2, 1220251881Speter ctx->wc_ctx, result_pool, iterpool)); 1221251881Speter } 1222251881Speter 1223251881Speter hdb.wc_ctx = ctx->wc_ctx; 1224251881Speter hdb.cancel_func = ctx->cancel_func; 1225251881Speter hdb.cancel_baton = ctx->cancel_baton; 1226251881Speter hdb.check_url_func = check_url_func; 1227251881Speter hdb.check_url_baton = check_url_baton; 1228269847Speter hdb.committables = *committables; 1229251881Speter 1230251881Speter SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository, 1231251881Speter handle_descendants, &hdb, iterpool)); 1232251881Speter 1233251881Speter /* Make sure that every path in danglers is part of the commit. */ 1234251881Speter for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi)) 1235251881Speter { 1236251881Speter const char *dangling_parent = svn__apr_hash_index_key(hi); 1237251881Speter 1238251881Speter svn_pool_clear(iterpool); 1239251881Speter 1240251881Speter if (! look_up_committable(*committables, dangling_parent, iterpool)) 1241251881Speter { 1242251881Speter const char *dangling_child = svn__apr_hash_index_val(hi); 1243251881Speter 1244251881Speter if (ctx->notify_func2 != NULL) 1245251881Speter { 1246251881Speter svn_wc_notify_t *notify; 1247251881Speter 1248251881Speter notify = svn_wc_create_notify(dangling_child, 1249251881Speter svn_wc_notify_failed_no_parent, 1250251881Speter scratch_pool); 1251251881Speter 1252251881Speter ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1253251881Speter } 1254251881Speter 1255251881Speter return svn_error_createf( 1256251881Speter SVN_ERR_ILLEGAL_TARGET, NULL, 1257251881Speter _("'%s' is not known to exist in the repository " 1258251881Speter "and is not part of the commit, " 1259251881Speter "yet its child '%s' is part of the commit"), 1260251881Speter /* Probably one or both of these is an entry, but 1261251881Speter safest to local_stylize just in case. */ 1262251881Speter svn_dirent_local_style(dangling_parent, iterpool), 1263251881Speter svn_dirent_local_style(dangling_child, iterpool)); 1264251881Speter } 1265251881Speter } 1266251881Speter 1267251881Speter svn_pool_destroy(iterpool); 1268251881Speter 1269251881Speter return SVN_NO_ERROR; 1270251881Speter} 1271251881Speter 1272251881Speterstruct copy_committables_baton 1273251881Speter{ 1274251881Speter svn_client_ctx_t *ctx; 1275251881Speter svn_client__committables_t *committables; 1276251881Speter apr_pool_t *result_pool; 1277251881Speter svn_client__check_url_kind_t check_url_func; 1278251881Speter void *check_url_baton; 1279251881Speter}; 1280251881Speter 1281251881Speterstatic svn_error_t * 1282251881Speterharvest_copy_committables(void *baton, void *item, apr_pool_t *pool) 1283251881Speter{ 1284251881Speter struct copy_committables_baton *btn = baton; 1285251881Speter svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item; 1286251881Speter const char *repos_root_url; 1287251881Speter const char *commit_relpath; 1288251881Speter struct handle_descendants_baton hdb; 1289251881Speter 1290251881Speter /* Read the entry for this SRC. */ 1291251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 1292251881Speter 1293251881Speter SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL, 1294251881Speter btn->ctx->wc_ctx, 1295251881Speter pair->src_abspath_or_url, 1296251881Speter pool, pool)); 1297251881Speter 1298251881Speter commit_relpath = svn_uri_skip_ancestor(repos_root_url, 1299251881Speter pair->dst_abspath_or_url, pool); 1300251881Speter 1301251881Speter /* Handle this SRC. */ 1302251881Speter SVN_ERR(harvest_committables(pair->src_abspath_or_url, 1303251881Speter btn->committables, NULL, 1304251881Speter commit_relpath, 1305251881Speter svn_depth_infinity, 1306251881Speter FALSE, /* JUST_LOCKED */ 1307251881Speter NULL /* changelists */, 1308251881Speter NULL, 1309251881Speter btn->check_url_func, 1310251881Speter btn->check_url_baton, 1311251881Speter btn->ctx->cancel_func, 1312251881Speter btn->ctx->cancel_baton, 1313251881Speter btn->ctx->notify_func2, 1314251881Speter btn->ctx->notify_baton2, 1315251881Speter btn->ctx->wc_ctx, btn->result_pool, pool)); 1316251881Speter 1317251881Speter hdb.wc_ctx = btn->ctx->wc_ctx; 1318251881Speter hdb.cancel_func = btn->ctx->cancel_func; 1319251881Speter hdb.cancel_baton = btn->ctx->cancel_baton; 1320251881Speter hdb.check_url_func = btn->check_url_func; 1321251881Speter hdb.check_url_baton = btn->check_url_baton; 1322269847Speter hdb.committables = btn->committables; 1323251881Speter 1324251881Speter SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository, 1325251881Speter handle_descendants, &hdb, pool)); 1326251881Speter 1327251881Speter return SVN_NO_ERROR; 1328251881Speter} 1329251881Speter 1330251881Speter 1331251881Speter 1332251881Spetersvn_error_t * 1333251881Spetersvn_client__get_copy_committables(svn_client__committables_t **committables, 1334251881Speter const apr_array_header_t *copy_pairs, 1335251881Speter svn_client__check_url_kind_t check_url_func, 1336251881Speter void *check_url_baton, 1337251881Speter svn_client_ctx_t *ctx, 1338251881Speter apr_pool_t *result_pool, 1339251881Speter apr_pool_t *scratch_pool) 1340251881Speter{ 1341251881Speter struct copy_committables_baton btn; 1342251881Speter 1343251881Speter /* Create the COMMITTABLES structure. */ 1344251881Speter create_committables(committables, result_pool); 1345251881Speter 1346251881Speter btn.ctx = ctx; 1347251881Speter btn.committables = *committables; 1348251881Speter btn.result_pool = result_pool; 1349251881Speter 1350251881Speter btn.check_url_func = check_url_func; 1351251881Speter btn.check_url_baton = check_url_baton; 1352251881Speter 1353251881Speter /* For each copy pair, harvest the committables for that pair into the 1354251881Speter committables hash. */ 1355251881Speter return svn_iter_apr_array(NULL, copy_pairs, 1356251881Speter harvest_copy_committables, &btn, scratch_pool); 1357251881Speter} 1358251881Speter 1359251881Speter 1360251881Speterint svn_client__sort_commit_item_urls(const void *a, const void *b) 1361251881Speter{ 1362251881Speter const svn_client_commit_item3_t *item1 1363251881Speter = *((const svn_client_commit_item3_t * const *) a); 1364251881Speter const svn_client_commit_item3_t *item2 1365251881Speter = *((const svn_client_commit_item3_t * const *) b); 1366251881Speter return svn_path_compare_paths(item1->url, item2->url); 1367251881Speter} 1368251881Speter 1369251881Speter 1370251881Speter 1371251881Spetersvn_error_t * 1372251881Spetersvn_client__condense_commit_items(const char **base_url, 1373251881Speter apr_array_header_t *commit_items, 1374251881Speter apr_pool_t *pool) 1375251881Speter{ 1376251881Speter apr_array_header_t *ci = commit_items; /* convenience */ 1377251881Speter const char *url; 1378251881Speter svn_client_commit_item3_t *item, *last_item = NULL; 1379251881Speter int i; 1380251881Speter 1381251881Speter SVN_ERR_ASSERT(ci && ci->nelts); 1382251881Speter 1383251881Speter /* Sort our commit items by their URLs. */ 1384251881Speter qsort(ci->elts, ci->nelts, 1385251881Speter ci->elt_size, svn_client__sort_commit_item_urls); 1386251881Speter 1387251881Speter /* Loop through the URLs, finding the longest usable ancestor common 1388251881Speter to all of them, and making sure there are no duplicate URLs. */ 1389251881Speter for (i = 0; i < ci->nelts; i++) 1390251881Speter { 1391251881Speter item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1392251881Speter url = item->url; 1393251881Speter 1394251881Speter if ((last_item) && (strcmp(last_item->url, url) == 0)) 1395251881Speter return svn_error_createf 1396251881Speter (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL, 1397251881Speter _("Cannot commit both '%s' and '%s' as they refer to the same URL"), 1398251881Speter svn_dirent_local_style(item->path, pool), 1399251881Speter svn_dirent_local_style(last_item->path, pool)); 1400251881Speter 1401251881Speter /* In the first iteration, our BASE_URL is just our only 1402251881Speter encountered commit URL to date. After that, we find the 1403251881Speter longest ancestor between the current BASE_URL and the current 1404251881Speter commit URL. */ 1405251881Speter if (i == 0) 1406251881Speter *base_url = apr_pstrdup(pool, url); 1407251881Speter else 1408251881Speter *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool); 1409251881Speter 1410251881Speter /* If our BASE_URL is itself a to-be-committed item, and it is 1411251881Speter anything other than an already-versioned directory with 1412251881Speter property mods, we'll call its parent directory URL the 1413251881Speter BASE_URL. Why? Because we can't have a file URL as our base 1414251881Speter -- period -- and all other directory operations (removal, 1415251881Speter addition, etc.) require that we open that directory's parent 1416251881Speter dir first. */ 1417251881Speter /* ### I don't understand the strlen()s here, hmmm. -kff */ 1418251881Speter if ((strlen(*base_url) == strlen(url)) 1419251881Speter && (! ((item->kind == svn_node_dir) 1420251881Speter && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS))) 1421251881Speter *base_url = svn_uri_dirname(*base_url, pool); 1422251881Speter 1423251881Speter /* Stash our item here for the next iteration. */ 1424251881Speter last_item = item; 1425251881Speter } 1426251881Speter 1427251881Speter /* Now that we've settled on a *BASE_URL, go hack that base off 1428251881Speter of all of our URLs and store it as session_relpath. */ 1429251881Speter for (i = 0; i < ci->nelts; i++) 1430251881Speter { 1431251881Speter svn_client_commit_item3_t *this_item 1432251881Speter = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1433251881Speter 1434251881Speter this_item->session_relpath = svn_uri_skip_ancestor(*base_url, 1435251881Speter this_item->url, pool); 1436251881Speter } 1437251881Speter#ifdef SVN_CLIENT_COMMIT_DEBUG 1438251881Speter /* ### TEMPORARY CODE ### */ 1439251881Speter SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url)); 1440251881Speter SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n")); 1441251881Speter for (i = 0; i < ci->nelts; i++) 1442251881Speter { 1443251881Speter svn_client_commit_item3_t *this_item 1444251881Speter = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); 1445251881Speter char flags[6]; 1446251881Speter flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1447251881Speter ? 'a' : '-'; 1448251881Speter flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1449251881Speter ? 'd' : '-'; 1450251881Speter flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1451251881Speter ? 't' : '-'; 1452251881Speter flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1453251881Speter ? 'p' : '-'; 1454251881Speter flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1455251881Speter ? 'c' : '-'; 1456251881Speter flags[5] = '\0'; 1457251881Speter SVN_DBG((" %s %6ld '%s' (%s)\n", 1458251881Speter flags, 1459251881Speter this_item->revision, 1460251881Speter this_item->url ? this_item->url : "", 1461251881Speter this_item->copyfrom_url ? this_item->copyfrom_url : "none")); 1462251881Speter } 1463251881Speter#endif /* SVN_CLIENT_COMMIT_DEBUG */ 1464251881Speter 1465251881Speter return SVN_NO_ERROR; 1466251881Speter} 1467251881Speter 1468251881Speter 1469251881Speterstruct file_mod_t 1470251881Speter{ 1471251881Speter const svn_client_commit_item3_t *item; 1472251881Speter void *file_baton; 1473251881Speter}; 1474251881Speter 1475251881Speter 1476251881Speter/* A baton for use while driving a path-based editor driver for commit */ 1477251881Speterstruct item_commit_baton 1478251881Speter{ 1479251881Speter const svn_delta_editor_t *editor; /* commit editor */ 1480251881Speter void *edit_baton; /* commit editor's baton */ 1481251881Speter apr_hash_t *file_mods; /* hash: path->file_mod_t */ 1482251881Speter const char *notify_path_prefix; /* notification path prefix 1483251881Speter (NULL is okay, else abs path) */ 1484251881Speter svn_client_ctx_t *ctx; /* client context baton */ 1485251881Speter apr_hash_t *commit_items; /* the committables */ 1486251881Speter const char *base_url; /* The session url for the commit */ 1487251881Speter}; 1488251881Speter 1489251881Speter 1490251881Speter/* Drive CALLBACK_BATON->editor with the change described by the item in 1491251881Speter * CALLBACK_BATON->commit_items that is keyed by PATH. If the change 1492251881Speter * includes a text mod, however, call the editor's file_open() function 1493251881Speter * but do not send the text mod to the editor; instead, add a mapping of 1494251881Speter * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods. 1495251881Speter * 1496251881Speter * Before driving the editor, call the cancellation and notification 1497251881Speter * callbacks in CALLBACK_BATON->ctx, if present. 1498251881Speter * 1499251881Speter * This implements svn_delta_path_driver_cb_func_t. */ 1500251881Speterstatic svn_error_t * 1501251881Speterdo_item_commit(void **dir_baton, 1502251881Speter void *parent_baton, 1503251881Speter void *callback_baton, 1504251881Speter const char *path, 1505251881Speter apr_pool_t *pool) 1506251881Speter{ 1507251881Speter struct item_commit_baton *icb = callback_baton; 1508251881Speter const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items, 1509251881Speter path); 1510251881Speter svn_node_kind_t kind = item->kind; 1511251881Speter void *file_baton = NULL; 1512251881Speter apr_pool_t *file_pool = NULL; 1513251881Speter const svn_delta_editor_t *editor = icb->editor; 1514251881Speter apr_hash_t *file_mods = icb->file_mods; 1515251881Speter svn_client_ctx_t *ctx = icb->ctx; 1516251881Speter svn_error_t *err; 1517251881Speter const char *local_abspath = NULL; 1518251881Speter 1519251881Speter /* Do some initializations. */ 1520251881Speter *dir_baton = NULL; 1521251881Speter if (item->kind != svn_node_none && item->path) 1522251881Speter { 1523251881Speter /* We always get an absolute path, see svn_client_commit_item3_t. */ 1524251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); 1525251881Speter local_abspath = item->path; 1526251881Speter } 1527251881Speter 1528251881Speter /* If this is a file with textual mods, we'll be keeping its baton 1529251881Speter around until the end of the commit. So just lump its memory into 1530251881Speter a single, big, all-the-file-batons-in-here pool. Otherwise, we 1531251881Speter can just use POOL, and trust our caller to clean that mess up. */ 1532251881Speter if ((kind == svn_node_file) 1533251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1534251881Speter file_pool = apr_hash_pool_get(file_mods); 1535251881Speter else 1536251881Speter file_pool = pool; 1537251881Speter 1538251881Speter /* Call the cancellation function. */ 1539251881Speter if (ctx->cancel_func) 1540251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1541251881Speter 1542251881Speter /* Validation. */ 1543251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 1544251881Speter { 1545251881Speter if (! item->copyfrom_url) 1546251881Speter return svn_error_createf 1547251881Speter (SVN_ERR_BAD_URL, NULL, 1548251881Speter _("Commit item '%s' has copy flag but no copyfrom URL"), 1549251881Speter svn_dirent_local_style(path, pool)); 1550251881Speter if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev)) 1551251881Speter return svn_error_createf 1552251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, 1553251881Speter _("Commit item '%s' has copy flag but an invalid revision"), 1554251881Speter svn_dirent_local_style(path, pool)); 1555251881Speter } 1556251881Speter 1557251881Speter /* If a feedback table was supplied by the application layer, 1558251881Speter describe what we're about to do to this item. */ 1559251881Speter if (ctx->notify_func2 && item->path) 1560251881Speter { 1561251881Speter const char *npath = item->path; 1562251881Speter svn_wc_notify_t *notify; 1563251881Speter 1564251881Speter if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1565251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 1566251881Speter { 1567251881Speter /* We don't print the "(bin)" notice for binary files when 1568251881Speter replacing, only when adding. So we don't bother to get 1569251881Speter the mime-type here. */ 1570251881Speter if (item->copyfrom_url) 1571251881Speter notify = svn_wc_create_notify(npath, 1572251881Speter svn_wc_notify_commit_copied_replaced, 1573251881Speter pool); 1574251881Speter else 1575251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced, 1576251881Speter pool); 1577251881Speter 1578251881Speter } 1579251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1580251881Speter { 1581251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted, 1582251881Speter pool); 1583251881Speter } 1584251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1585251881Speter { 1586251881Speter if (item->copyfrom_url) 1587251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied, 1588251881Speter pool); 1589251881Speter else 1590251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added, 1591251881Speter pool); 1592251881Speter 1593251881Speter if (item->kind == svn_node_file) 1594251881Speter { 1595251881Speter const svn_string_t *propval; 1596251881Speter 1597251881Speter SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath, 1598251881Speter SVN_PROP_MIME_TYPE, pool, pool)); 1599251881Speter 1600251881Speter if (propval) 1601251881Speter notify->mime_type = propval->data; 1602251881Speter } 1603251881Speter } 1604251881Speter else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1605251881Speter || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)) 1606251881Speter { 1607251881Speter notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified, 1608251881Speter pool); 1609251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 1610251881Speter notify->content_state = svn_wc_notify_state_changed; 1611251881Speter else 1612251881Speter notify->content_state = svn_wc_notify_state_unchanged; 1613251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1614251881Speter notify->prop_state = svn_wc_notify_state_changed; 1615251881Speter else 1616251881Speter notify->prop_state = svn_wc_notify_state_unchanged; 1617251881Speter } 1618251881Speter else 1619251881Speter notify = NULL; 1620251881Speter 1621251881Speter if (notify) 1622251881Speter { 1623251881Speter notify->kind = item->kind; 1624251881Speter notify->path_prefix = icb->notify_path_prefix; 1625251881Speter (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1626251881Speter } 1627251881Speter } 1628251881Speter 1629251881Speter /* If this item is supposed to be deleted, do so. */ 1630251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 1631251881Speter { 1632251881Speter SVN_ERR_ASSERT(parent_baton); 1633251881Speter err = editor->delete_entry(path, item->revision, 1634251881Speter parent_baton, pool); 1635251881Speter 1636251881Speter if (err) 1637251881Speter goto fixup_error; 1638251881Speter } 1639251881Speter 1640251881Speter /* If this item is supposed to be added, do so. */ 1641251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1642251881Speter { 1643251881Speter if (kind == svn_node_file) 1644251881Speter { 1645251881Speter SVN_ERR_ASSERT(parent_baton); 1646251881Speter err = editor->add_file( 1647251881Speter path, parent_baton, item->copyfrom_url, 1648251881Speter item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1649251881Speter file_pool, &file_baton); 1650251881Speter } 1651251881Speter else /* May be svn_node_none when adding parent dirs for a copy. */ 1652251881Speter { 1653251881Speter SVN_ERR_ASSERT(parent_baton); 1654251881Speter err = editor->add_directory( 1655251881Speter path, parent_baton, item->copyfrom_url, 1656251881Speter item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, 1657251881Speter pool, dir_baton); 1658251881Speter } 1659251881Speter 1660251881Speter if (err) 1661251881Speter goto fixup_error; 1662251881Speter 1663251881Speter /* Set other prop-changes, if available in the baton */ 1664251881Speter if (item->outgoing_prop_changes) 1665251881Speter { 1666251881Speter svn_prop_t *prop; 1667251881Speter apr_array_header_t *prop_changes = item->outgoing_prop_changes; 1668251881Speter int ctr; 1669251881Speter for (ctr = 0; ctr < prop_changes->nelts; ctr++) 1670251881Speter { 1671251881Speter prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *); 1672251881Speter if (kind == svn_node_file) 1673251881Speter { 1674251881Speter err = editor->change_file_prop(file_baton, prop->name, 1675251881Speter prop->value, pool); 1676251881Speter } 1677251881Speter else 1678251881Speter { 1679251881Speter err = editor->change_dir_prop(*dir_baton, prop->name, 1680251881Speter prop->value, pool); 1681251881Speter } 1682251881Speter 1683251881Speter if (err) 1684251881Speter goto fixup_error; 1685251881Speter } 1686251881Speter } 1687251881Speter } 1688251881Speter 1689251881Speter /* Now handle property mods. */ 1690251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 1691251881Speter { 1692251881Speter if (kind == svn_node_file) 1693251881Speter { 1694251881Speter if (! file_baton) 1695251881Speter { 1696251881Speter SVN_ERR_ASSERT(parent_baton); 1697251881Speter err = editor->open_file(path, parent_baton, 1698251881Speter item->revision, 1699251881Speter file_pool, &file_baton); 1700251881Speter 1701251881Speter if (err) 1702251881Speter goto fixup_error; 1703251881Speter } 1704251881Speter } 1705251881Speter else 1706251881Speter { 1707251881Speter if (! *dir_baton) 1708251881Speter { 1709251881Speter if (! parent_baton) 1710251881Speter { 1711251881Speter err = editor->open_root(icb->edit_baton, item->revision, 1712251881Speter pool, dir_baton); 1713251881Speter } 1714251881Speter else 1715251881Speter { 1716251881Speter err = editor->open_directory(path, parent_baton, 1717251881Speter item->revision, 1718251881Speter pool, dir_baton); 1719251881Speter } 1720251881Speter 1721251881Speter if (err) 1722251881Speter goto fixup_error; 1723251881Speter } 1724251881Speter } 1725251881Speter 1726251881Speter /* When committing a directory that no longer exists in the 1727251881Speter repository, a "not found" error does not occur immediately 1728251881Speter upon opening the directory. It appears here during the delta 1729251881Speter transmisssion. */ 1730251881Speter err = svn_wc_transmit_prop_deltas2( 1731251881Speter ctx->wc_ctx, local_abspath, editor, 1732251881Speter (kind == svn_node_dir) ? *dir_baton : file_baton, pool); 1733251881Speter 1734251881Speter if (err) 1735251881Speter goto fixup_error; 1736251881Speter 1737251881Speter /* Make any additional client -> repository prop changes. */ 1738251881Speter if (item->outgoing_prop_changes) 1739251881Speter { 1740251881Speter svn_prop_t *prop; 1741251881Speter int i; 1742251881Speter 1743251881Speter for (i = 0; i < item->outgoing_prop_changes->nelts; i++) 1744251881Speter { 1745251881Speter prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i, 1746251881Speter svn_prop_t *); 1747251881Speter if (kind == svn_node_file) 1748251881Speter { 1749251881Speter err = editor->change_file_prop(file_baton, prop->name, 1750251881Speter prop->value, pool); 1751251881Speter } 1752251881Speter else 1753251881Speter { 1754251881Speter err = editor->change_dir_prop(*dir_baton, prop->name, 1755251881Speter prop->value, pool); 1756251881Speter } 1757251881Speter 1758251881Speter if (err) 1759251881Speter goto fixup_error; 1760251881Speter } 1761251881Speter } 1762251881Speter } 1763251881Speter 1764251881Speter /* Finally, handle text mods (in that we need to open a file if it 1765251881Speter hasn't already been opened, and we need to put the file baton in 1766251881Speter our FILES hash). */ 1767251881Speter if ((kind == svn_node_file) 1768251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) 1769251881Speter { 1770251881Speter struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod)); 1771251881Speter 1772251881Speter if (! file_baton) 1773251881Speter { 1774251881Speter SVN_ERR_ASSERT(parent_baton); 1775251881Speter err = editor->open_file(path, parent_baton, 1776251881Speter item->revision, 1777251881Speter file_pool, &file_baton); 1778251881Speter 1779251881Speter if (err) 1780251881Speter goto fixup_error; 1781251881Speter } 1782251881Speter 1783251881Speter /* Add this file mod to the FILE_MODS hash. */ 1784251881Speter mod->item = item; 1785251881Speter mod->file_baton = file_baton; 1786251881Speter svn_hash_sets(file_mods, item->session_relpath, mod); 1787251881Speter } 1788251881Speter else if (file_baton) 1789251881Speter { 1790251881Speter /* Close any outstanding file batons that didn't get caught by 1791251881Speter the "has local mods" conditional above. */ 1792251881Speter err = editor->close_file(file_baton, NULL, file_pool); 1793251881Speter 1794251881Speter if (err) 1795251881Speter goto fixup_error; 1796251881Speter } 1797251881Speter 1798251881Speter return SVN_NO_ERROR; 1799251881Speter 1800251881Speterfixup_error: 1801251881Speter return svn_error_trace(fixup_commit_error(local_abspath, 1802251881Speter icb->base_url, 1803251881Speter path, kind, 1804251881Speter err, ctx, pool)); 1805251881Speter} 1806251881Speter 1807251881Spetersvn_error_t * 1808251881Spetersvn_client__do_commit(const char *base_url, 1809251881Speter const apr_array_header_t *commit_items, 1810251881Speter const svn_delta_editor_t *editor, 1811251881Speter void *edit_baton, 1812251881Speter const char *notify_path_prefix, 1813251881Speter apr_hash_t **sha1_checksums, 1814251881Speter svn_client_ctx_t *ctx, 1815251881Speter apr_pool_t *result_pool, 1816251881Speter apr_pool_t *scratch_pool) 1817251881Speter{ 1818251881Speter apr_hash_t *file_mods = apr_hash_make(scratch_pool); 1819251881Speter apr_hash_t *items_hash = apr_hash_make(scratch_pool); 1820251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1821251881Speter apr_hash_index_t *hi; 1822251881Speter int i; 1823251881Speter struct item_commit_baton cb_baton; 1824251881Speter apr_array_header_t *paths = 1825251881Speter apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *)); 1826251881Speter 1827251881Speter /* Ditto for the checksums. */ 1828251881Speter if (sha1_checksums) 1829251881Speter *sha1_checksums = apr_hash_make(result_pool); 1830251881Speter 1831251881Speter /* Build a hash from our COMMIT_ITEMS array, keyed on the 1832251881Speter relative paths (which come from the item URLs). And 1833251881Speter keep an array of those decoded paths, too. */ 1834251881Speter for (i = 0; i < commit_items->nelts; i++) 1835251881Speter { 1836251881Speter svn_client_commit_item3_t *item = 1837251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1838251881Speter const char *path = item->session_relpath; 1839251881Speter svn_hash_sets(items_hash, path, item); 1840251881Speter APR_ARRAY_PUSH(paths, const char *) = path; 1841251881Speter } 1842251881Speter 1843251881Speter /* Setup the callback baton. */ 1844251881Speter cb_baton.editor = editor; 1845251881Speter cb_baton.edit_baton = edit_baton; 1846251881Speter cb_baton.file_mods = file_mods; 1847251881Speter cb_baton.notify_path_prefix = notify_path_prefix; 1848251881Speter cb_baton.ctx = ctx; 1849251881Speter cb_baton.commit_items = items_hash; 1850251881Speter cb_baton.base_url = base_url; 1851251881Speter 1852251881Speter /* Drive the commit editor! */ 1853251881Speter SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1854251881Speter do_item_commit, &cb_baton, scratch_pool)); 1855251881Speter 1856251881Speter /* Transmit outstanding text deltas. */ 1857251881Speter for (hi = apr_hash_first(scratch_pool, file_mods); 1858251881Speter hi; 1859251881Speter hi = apr_hash_next(hi)) 1860251881Speter { 1861251881Speter struct file_mod_t *mod = svn__apr_hash_index_val(hi); 1862251881Speter const svn_client_commit_item3_t *item = mod->item; 1863251881Speter const svn_checksum_t *new_text_base_md5_checksum; 1864251881Speter const svn_checksum_t *new_text_base_sha1_checksum; 1865251881Speter svn_boolean_t fulltext = FALSE; 1866251881Speter svn_error_t *err; 1867251881Speter 1868251881Speter svn_pool_clear(iterpool); 1869251881Speter 1870251881Speter /* Transmit the entry. */ 1871251881Speter if (ctx->cancel_func) 1872251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1873251881Speter 1874251881Speter if (ctx->notify_func2) 1875251881Speter { 1876251881Speter svn_wc_notify_t *notify; 1877251881Speter notify = svn_wc_create_notify(item->path, 1878251881Speter svn_wc_notify_commit_postfix_txdelta, 1879251881Speter iterpool); 1880251881Speter notify->kind = svn_node_file; 1881251881Speter notify->path_prefix = notify_path_prefix; 1882251881Speter ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 1883251881Speter } 1884251881Speter 1885251881Speter /* If the node has no history, transmit full text */ 1886251881Speter if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 1887251881Speter && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) 1888251881Speter fulltext = TRUE; 1889251881Speter 1890251881Speter err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum, 1891251881Speter &new_text_base_sha1_checksum, 1892251881Speter ctx->wc_ctx, item->path, 1893251881Speter fulltext, editor, mod->file_baton, 1894251881Speter result_pool, iterpool); 1895251881Speter 1896251881Speter if (err) 1897251881Speter { 1898251881Speter svn_pool_destroy(iterpool); /* Close tempfiles */ 1899251881Speter return svn_error_trace(fixup_commit_error(item->path, 1900251881Speter base_url, 1901251881Speter item->session_relpath, 1902251881Speter svn_node_file, 1903251881Speter err, ctx, scratch_pool)); 1904251881Speter } 1905251881Speter 1906251881Speter if (sha1_checksums) 1907251881Speter svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum); 1908251881Speter } 1909251881Speter 1910251881Speter svn_pool_destroy(iterpool); 1911251881Speter 1912251881Speter /* Close the edit. */ 1913251881Speter return svn_error_trace(editor->close_edit(edit_baton, scratch_pool)); 1914251881Speter} 1915251881Speter 1916251881Speter 1917251881Spetersvn_error_t * 1918251881Spetersvn_client__get_log_msg(const char **log_msg, 1919251881Speter const char **tmp_file, 1920251881Speter const apr_array_header_t *commit_items, 1921251881Speter svn_client_ctx_t *ctx, 1922251881Speter apr_pool_t *pool) 1923251881Speter{ 1924251881Speter if (ctx->log_msg_func3) 1925251881Speter { 1926251881Speter /* The client provided a callback function for the current API. 1927251881Speter Forward the call to it directly. */ 1928251881Speter return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items, 1929251881Speter ctx->log_msg_baton3, pool); 1930251881Speter } 1931251881Speter else if (ctx->log_msg_func2 || ctx->log_msg_func) 1932251881Speter { 1933251881Speter /* The client provided a pre-1.5 (or pre-1.3) API callback 1934251881Speter function. Convert the commit_items list to the appropriate 1935251881Speter type, and forward call to it. */ 1936251881Speter svn_error_t *err; 1937251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 1938251881Speter apr_array_header_t *old_commit_items = 1939251881Speter apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*)); 1940251881Speter 1941251881Speter int i; 1942251881Speter for (i = 0; i < commit_items->nelts; i++) 1943251881Speter { 1944251881Speter svn_client_commit_item3_t *item = 1945251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1946251881Speter 1947251881Speter if (ctx->log_msg_func2) 1948251881Speter { 1949251881Speter svn_client_commit_item2_t *old_item = 1950251881Speter apr_pcalloc(scratch_pool, sizeof(*old_item)); 1951251881Speter 1952251881Speter old_item->path = item->path; 1953251881Speter old_item->kind = item->kind; 1954251881Speter old_item->url = item->url; 1955251881Speter old_item->revision = item->revision; 1956251881Speter old_item->copyfrom_url = item->copyfrom_url; 1957251881Speter old_item->copyfrom_rev = item->copyfrom_rev; 1958251881Speter old_item->state_flags = item->state_flags; 1959251881Speter old_item->wcprop_changes = item->incoming_prop_changes; 1960251881Speter 1961251881Speter APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) = 1962251881Speter old_item; 1963251881Speter } 1964251881Speter else /* ctx->log_msg_func */ 1965251881Speter { 1966251881Speter svn_client_commit_item_t *old_item = 1967251881Speter apr_pcalloc(scratch_pool, sizeof(*old_item)); 1968251881Speter 1969251881Speter old_item->path = item->path; 1970251881Speter old_item->kind = item->kind; 1971251881Speter old_item->url = item->url; 1972251881Speter /* The pre-1.3 API used the revision field for copyfrom_rev 1973251881Speter and revision depeding of copyfrom_url. */ 1974251881Speter old_item->revision = item->copyfrom_url ? 1975251881Speter item->copyfrom_rev : item->revision; 1976251881Speter old_item->copyfrom_url = item->copyfrom_url; 1977251881Speter old_item->state_flags = item->state_flags; 1978251881Speter old_item->wcprop_changes = item->incoming_prop_changes; 1979251881Speter 1980251881Speter APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) = 1981251881Speter old_item; 1982251881Speter } 1983251881Speter } 1984251881Speter 1985251881Speter if (ctx->log_msg_func2) 1986251881Speter err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items, 1987251881Speter ctx->log_msg_baton2, pool); 1988251881Speter else 1989251881Speter err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items, 1990251881Speter ctx->log_msg_baton, pool); 1991251881Speter svn_pool_destroy(scratch_pool); 1992251881Speter return err; 1993251881Speter } 1994251881Speter else 1995251881Speter { 1996251881Speter /* No log message callback was provided by the client. */ 1997251881Speter *log_msg = ""; 1998251881Speter *tmp_file = NULL; 1999251881Speter return SVN_NO_ERROR; 2000251881Speter } 2001251881Speter} 2002251881Speter 2003251881Spetersvn_error_t * 2004251881Spetersvn_client__ensure_revprop_table(apr_hash_t **revprop_table_out, 2005251881Speter const apr_hash_t *revprop_table_in, 2006251881Speter const char *log_msg, 2007251881Speter svn_client_ctx_t *ctx, 2008251881Speter apr_pool_t *pool) 2009251881Speter{ 2010251881Speter apr_hash_t *new_revprop_table; 2011251881Speter if (revprop_table_in) 2012251881Speter { 2013251881Speter if (svn_prop_has_svn_prop(revprop_table_in, pool)) 2014251881Speter return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 2015251881Speter _("Standard properties can't be set " 2016251881Speter "explicitly as revision properties")); 2017251881Speter new_revprop_table = apr_hash_copy(pool, revprop_table_in); 2018251881Speter } 2019251881Speter else 2020251881Speter { 2021251881Speter new_revprop_table = apr_hash_make(pool); 2022251881Speter } 2023251881Speter svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG, 2024251881Speter svn_string_create(log_msg, pool)); 2025251881Speter *revprop_table_out = new_revprop_table; 2026251881Speter return SVN_NO_ERROR; 2027251881Speter} 2028