1251881Speter/* 2251881Speter * delete.c: wrappers around wc delete functionality. 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 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <apr_file_io.h> 31251881Speter#include "svn_hash.h" 32251881Speter#include "svn_types.h" 33251881Speter#include "svn_pools.h" 34251881Speter#include "svn_wc.h" 35251881Speter#include "svn_client.h" 36251881Speter#include "svn_error.h" 37251881Speter#include "svn_dirent_uri.h" 38251881Speter#include "svn_path.h" 39251881Speter#include "client.h" 40251881Speter 41251881Speter#include "private/svn_client_private.h" 42251881Speter#include "private/svn_wc_private.h" 43251881Speter#include "private/svn_ra_private.h" 44251881Speter 45251881Speter#include "svn_private_config.h" 46251881Speter 47251881Speter 48251881Speter/*** Code. ***/ 49251881Speter 50251881Speter/* Baton for find_undeletables */ 51251881Speterstruct can_delete_baton_t 52251881Speter{ 53251881Speter const char *root_abspath; 54251881Speter svn_boolean_t target_missing; 55251881Speter}; 56251881Speter 57251881Speter/* An svn_wc_status_func4_t callback function for finding 58251881Speter status structures which are not safely deletable. */ 59251881Speterstatic svn_error_t * 60251881Speterfind_undeletables(void *baton, 61251881Speter const char *local_abspath, 62251881Speter const svn_wc_status3_t *status, 63251881Speter apr_pool_t *pool) 64251881Speter{ 65251881Speter if (status->node_status == svn_wc_status_missing) 66251881Speter { 67251881Speter struct can_delete_baton_t *cdt = baton; 68251881Speter 69251881Speter if (strcmp(cdt->root_abspath, local_abspath) == 0) 70251881Speter cdt->target_missing = TRUE; 71251881Speter } 72251881Speter 73251881Speter /* Check for error-ful states. */ 74251881Speter if (status->node_status == svn_wc_status_obstructed) 75251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 76251881Speter _("'%s' is in the way of the resource " 77251881Speter "actually under version control"), 78251881Speter svn_dirent_local_style(local_abspath, pool)); 79251881Speter else if (! status->versioned) 80251881Speter return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, 81251881Speter _("'%s' is not under version control"), 82251881Speter svn_dirent_local_style(local_abspath, pool)); 83251881Speter else if ((status->node_status == svn_wc_status_added 84251881Speter || status->node_status == svn_wc_status_replaced) 85251881Speter && status->text_status == svn_wc_status_normal 86251881Speter && (status->prop_status == svn_wc_status_normal 87251881Speter || status->prop_status == svn_wc_status_none)) 88251881Speter { 89251881Speter /* Unmodified copy. Go ahead, remove it */ 90251881Speter } 91251881Speter else if (status->node_status != svn_wc_status_normal 92251881Speter && status->node_status != svn_wc_status_deleted 93251881Speter && status->node_status != svn_wc_status_missing) 94251881Speter return svn_error_createf(SVN_ERR_CLIENT_MODIFIED, NULL, 95251881Speter _("'%s' has local modifications -- commit or " 96251881Speter "revert them first"), 97251881Speter svn_dirent_local_style(local_abspath, pool)); 98251881Speter 99251881Speter return SVN_NO_ERROR; 100251881Speter} 101251881Speter 102251881Speter/* Check whether LOCAL_ABSPATH is an external and raise an error if it is. 103251881Speter 104251881Speter A file external should not be deleted since the file external is 105251881Speter implemented as a switched file and it would delete the file the 106251881Speter file external is switched to, which is not the behavior the user 107251881Speter would probably want. 108251881Speter 109251881Speter A directory external should not be deleted since it is the root 110251881Speter of a different working copy. */ 111251881Speterstatic svn_error_t * 112251881Spetercheck_external(const char *local_abspath, 113251881Speter svn_client_ctx_t *ctx, 114251881Speter apr_pool_t *scratch_pool) 115251881Speter{ 116251881Speter svn_node_kind_t external_kind; 117251881Speter const char *defining_abspath; 118251881Speter 119251881Speter SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL, 120251881Speter NULL, NULL, 121251881Speter ctx->wc_ctx, local_abspath, 122251881Speter local_abspath, TRUE, 123251881Speter scratch_pool, scratch_pool)); 124251881Speter 125251881Speter if (external_kind != svn_node_none) 126251881Speter return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL, 127251881Speter _("Cannot remove the external at '%s'; " 128251881Speter "please edit or delete the svn:externals " 129251881Speter "property on '%s'"), 130251881Speter svn_dirent_local_style(local_abspath, 131251881Speter scratch_pool), 132251881Speter svn_dirent_local_style(defining_abspath, 133251881Speter scratch_pool)); 134251881Speter 135251881Speter return SVN_NO_ERROR; 136251881Speter} 137251881Speter 138251881Speter/* Verify that the path can be deleted without losing stuff, 139251881Speter i.e. ensure that there are no modified or unversioned resources 140251881Speter under PATH. This is similar to checking the output of the status 141251881Speter command. CTX is used for the client's config options. POOL is 142251881Speter used for all temporary allocations. */ 143251881Speterstatic svn_error_t * 144251881Spetercan_delete_node(svn_boolean_t *target_missing, 145251881Speter const char *local_abspath, 146251881Speter svn_client_ctx_t *ctx, 147251881Speter apr_pool_t *scratch_pool) 148251881Speter{ 149251881Speter apr_array_header_t *ignores; 150251881Speter struct can_delete_baton_t cdt; 151251881Speter 152251881Speter /* Use an infinite-depth status check to see if there's anything in 153251881Speter or under PATH which would make it unsafe for deletion. The 154251881Speter status callback function find_undeletables() makes the 155251881Speter determination, returning an error if it finds anything that shouldn't 156251881Speter be deleted. */ 157251881Speter 158251881Speter SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool)); 159251881Speter 160251881Speter cdt.root_abspath = local_abspath; 161251881Speter cdt.target_missing = FALSE; 162251881Speter 163251881Speter SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, 164251881Speter local_abspath, 165251881Speter svn_depth_infinity, 166251881Speter FALSE /* get_all */, 167251881Speter FALSE /* no_ignore */, 168251881Speter FALSE /* ignore_text_mod */, 169251881Speter ignores, 170251881Speter find_undeletables, &cdt, 171251881Speter ctx->cancel_func, 172251881Speter ctx->cancel_baton, 173251881Speter scratch_pool)); 174251881Speter 175251881Speter if (target_missing) 176251881Speter *target_missing = cdt.target_missing; 177251881Speter 178251881Speter return SVN_NO_ERROR; 179251881Speter} 180251881Speter 181251881Speter 182251881Speterstatic svn_error_t * 183251881Speterpath_driver_cb_func(void **dir_baton, 184362181Sdim const svn_delta_editor_t *editor, 185362181Sdim void *edit_baton, 186251881Speter void *parent_baton, 187251881Speter void *callback_baton, 188251881Speter const char *path, 189251881Speter apr_pool_t *pool) 190251881Speter{ 191251881Speter *dir_baton = NULL; 192251881Speter return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool); 193251881Speter} 194251881Speter 195251881Speterstatic svn_error_t * 196251881Spetersingle_repos_delete(svn_ra_session_t *ra_session, 197269833Speter const char *base_uri, 198251881Speter const apr_array_header_t *relpaths, 199251881Speter const apr_hash_t *revprop_table, 200251881Speter svn_commit_callback2_t commit_callback, 201251881Speter void *commit_baton, 202251881Speter svn_client_ctx_t *ctx, 203251881Speter apr_pool_t *pool) 204251881Speter{ 205251881Speter const svn_delta_editor_t *editor; 206251881Speter apr_hash_t *commit_revprops; 207251881Speter void *edit_baton; 208251881Speter const char *log_msg; 209251881Speter int i; 210251881Speter svn_error_t *err; 211251881Speter 212251881Speter /* Create new commit items and add them to the array. */ 213251881Speter if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 214251881Speter { 215251881Speter svn_client_commit_item3_t *item; 216251881Speter const char *tmp_file; 217251881Speter apr_array_header_t *commit_items 218251881Speter = apr_array_make(pool, relpaths->nelts, sizeof(item)); 219251881Speter 220251881Speter for (i = 0; i < relpaths->nelts; i++) 221251881Speter { 222251881Speter const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *); 223251881Speter 224251881Speter item = svn_client_commit_item3_create(pool); 225269833Speter item->url = svn_path_url_add_component2(base_uri, relpath, pool); 226251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 227251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 228251881Speter } 229251881Speter SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, 230251881Speter ctx, pool)); 231251881Speter if (! log_msg) 232251881Speter return SVN_NO_ERROR; 233251881Speter } 234251881Speter else 235251881Speter log_msg = ""; 236251881Speter 237251881Speter SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 238251881Speter log_msg, ctx, pool)); 239251881Speter 240251881Speter /* Fetch RA commit editor */ 241251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 242251881Speter svn_client__get_shim_callbacks(ctx->wc_ctx, 243251881Speter NULL, pool))); 244251881Speter SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 245251881Speter commit_revprops, 246251881Speter commit_callback, 247251881Speter commit_baton, 248251881Speter NULL, TRUE, /* No lock tokens */ 249251881Speter pool)); 250251881Speter 251251881Speter /* Call the path-based editor driver. */ 252362181Sdim err = svn_delta_path_driver3(editor, edit_baton, relpaths, TRUE, 253362181Sdim path_driver_cb_func, NULL, pool); 254251881Speter 255251881Speter if (err) 256251881Speter { 257251881Speter return svn_error_trace( 258251881Speter svn_error_compose_create(err, 259251881Speter editor->abort_edit(edit_baton, pool))); 260251881Speter } 261251881Speter 262289180Speter if (ctx->notify_func2) 263289180Speter { 264289180Speter svn_wc_notify_t *notify; 265289180Speter notify = svn_wc_create_notify_url(base_uri, 266289180Speter svn_wc_notify_commit_finalizing, 267289180Speter pool); 268289180Speter ctx->notify_func2(ctx->notify_baton2, notify, pool); 269289180Speter } 270289180Speter 271251881Speter /* Close the edit. */ 272251881Speter return svn_error_trace(editor->close_edit(edit_baton, pool)); 273251881Speter} 274251881Speter 275251881Speter 276251881Speter/* Structure for tracking remote delete targets associated with a 277251881Speter specific repository. */ 278251881Speterstruct repos_deletables_t 279251881Speter{ 280251881Speter svn_ra_session_t *ra_session; 281251881Speter apr_array_header_t *target_uris; 282251881Speter}; 283251881Speter 284251881Speter 285251881Speterstatic svn_error_t * 286251881Speterdelete_urls_multi_repos(const apr_array_header_t *uris, 287251881Speter const apr_hash_t *revprop_table, 288251881Speter svn_commit_callback2_t commit_callback, 289251881Speter void *commit_baton, 290251881Speter svn_client_ctx_t *ctx, 291251881Speter apr_pool_t *pool) 292251881Speter{ 293251881Speter apr_hash_t *deletables = apr_hash_make(pool); 294251881Speter apr_pool_t *iterpool; 295251881Speter apr_hash_index_t *hi; 296251881Speter int i; 297251881Speter 298251881Speter /* Create a hash mapping repository root URLs -> repos_deletables_t * 299251881Speter structures. */ 300251881Speter for (i = 0; i < uris->nelts; i++) 301251881Speter { 302251881Speter const char *uri = APR_ARRAY_IDX(uris, i, const char *); 303251881Speter struct repos_deletables_t *repos_deletables = NULL; 304251881Speter const char *repos_relpath; 305251881Speter svn_node_kind_t kind; 306251881Speter 307251881Speter for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi)) 308251881Speter { 309289180Speter const char *repos_root = apr_hash_this_key(hi); 310251881Speter 311251881Speter repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool); 312251881Speter if (repos_relpath) 313251881Speter { 314251881Speter /* Great! We've found another URI underneath this 315251881Speter session. We'll pick out the related RA session for 316251881Speter use later, store the new target, and move on. */ 317289180Speter repos_deletables = apr_hash_this_val(hi); 318251881Speter APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) = 319251881Speter apr_pstrdup(pool, uri); 320251881Speter break; 321251881Speter } 322251881Speter } 323251881Speter 324251881Speter /* If we haven't created a repos_deletable structure for this 325251881Speter delete target, we need to do. That means opening up an RA 326251881Speter session and initializing its targets list. */ 327251881Speter if (!repos_deletables) 328251881Speter { 329251881Speter svn_ra_session_t *ra_session = NULL; 330251881Speter const char *repos_root; 331251881Speter apr_array_header_t *target_uris; 332251881Speter 333251881Speter /* Open an RA session to (ultimately) the root of the 334251881Speter repository in which URI is found. */ 335251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL, 336251881Speter ctx, pool, pool)); 337251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); 338251881Speter SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool)); 339251881Speter repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool); 340251881Speter 341251881Speter /* Make a new relpaths list for this repository, and add 342251881Speter this URI's relpath to it. */ 343251881Speter target_uris = apr_array_make(pool, 1, sizeof(const char *)); 344251881Speter APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri); 345251881Speter 346251881Speter /* Build our repos_deletables_t item and stash it in the 347251881Speter hash. */ 348251881Speter repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables)); 349251881Speter repos_deletables->ra_session = ra_session; 350251881Speter repos_deletables->target_uris = target_uris; 351251881Speter svn_hash_sets(deletables, repos_root, repos_deletables); 352251881Speter } 353251881Speter 354251881Speter /* If we get here, we should have been able to calculate a 355251881Speter repos_relpath for this URI. Let's make sure. (We return an 356251881Speter RA error code otherwise for 1.6 compatibility.) */ 357251881Speter if (!repos_relpath || !*repos_relpath) 358251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 359289180Speter _("URL '%s' not within a repository"), uri); 360251881Speter 361251881Speter /* Now, test to see if the thing actually exists in HEAD. */ 362251881Speter SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath, 363251881Speter SVN_INVALID_REVNUM, &kind, pool)); 364251881Speter if (kind == svn_node_none) 365251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 366289180Speter _("URL '%s' does not exist"), uri); 367251881Speter } 368251881Speter 369251881Speter /* Now we iterate over the DELETABLES hash, issuing a commit for 370251881Speter each repository with its associated collected targets. */ 371251881Speter iterpool = svn_pool_create(pool); 372251881Speter for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi)) 373251881Speter { 374289180Speter struct repos_deletables_t *repos_deletables = apr_hash_this_val(hi); 375251881Speter const char *base_uri; 376251881Speter apr_array_header_t *target_relpaths; 377251881Speter 378251881Speter svn_pool_clear(iterpool); 379251881Speter 380251881Speter /* We want to anchor the commit on the longest common path 381251881Speter across the targets for this one repository. If, however, one 382251881Speter of our targets is that longest common path, we need instead 383251881Speter anchor the commit on that path's immediate parent. Because 384251881Speter we're asking svn_uri_condense_targets() to remove 385251881Speter redundancies, this situation should be detectable by their 386251881Speter being returned either a) only a single, empty-path, target 387251881Speter relpath, or b) no target relpaths at all. */ 388251881Speter SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths, 389251881Speter repos_deletables->target_uris, 390251881Speter TRUE, iterpool, iterpool)); 391251881Speter SVN_ERR_ASSERT(!svn_path_is_empty(base_uri)); 392251881Speter if (target_relpaths->nelts == 0) 393251881Speter { 394251881Speter const char *target_relpath; 395251881Speter 396251881Speter svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool); 397251881Speter APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath; 398251881Speter } 399251881Speter else if ((target_relpaths->nelts == 1) 400251881Speter && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0, 401251881Speter const char *)))) 402251881Speter { 403251881Speter const char *target_relpath; 404251881Speter 405251881Speter svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool); 406251881Speter APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath; 407251881Speter } 408251881Speter 409251881Speter SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool)); 410269833Speter SVN_ERR(single_repos_delete(repos_deletables->ra_session, base_uri, 411251881Speter target_relpaths, 412251881Speter revprop_table, commit_callback, 413251881Speter commit_baton, ctx, iterpool)); 414251881Speter } 415251881Speter svn_pool_destroy(iterpool); 416251881Speter 417251881Speter return SVN_NO_ERROR; 418251881Speter} 419251881Speter 420251881Spetersvn_error_t * 421251881Spetersvn_client__wc_delete(const char *local_abspath, 422251881Speter svn_boolean_t force, 423251881Speter svn_boolean_t dry_run, 424251881Speter svn_boolean_t keep_local, 425251881Speter svn_wc_notify_func2_t notify_func, 426251881Speter void *notify_baton, 427251881Speter svn_client_ctx_t *ctx, 428251881Speter apr_pool_t *pool) 429251881Speter{ 430251881Speter svn_boolean_t target_missing = FALSE; 431251881Speter 432251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 433251881Speter 434251881Speter SVN_ERR(check_external(local_abspath, ctx, pool)); 435251881Speter 436251881Speter if (!force && !keep_local) 437251881Speter /* Verify that there are no "awkward" files */ 438251881Speter SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool)); 439251881Speter 440251881Speter if (!dry_run) 441251881Speter /* Mark the entry for commit deletion and perform wc deletion */ 442251881Speter return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath, 443251881Speter keep_local || target_missing 444251881Speter /*keep_local */, 445251881Speter TRUE /* delete_unversioned */, 446251881Speter ctx->cancel_func, ctx->cancel_baton, 447251881Speter notify_func, notify_baton, pool)); 448251881Speter 449251881Speter return SVN_NO_ERROR; 450251881Speter} 451251881Speter 452251881Spetersvn_error_t * 453251881Spetersvn_client__wc_delete_many(const apr_array_header_t *targets, 454251881Speter svn_boolean_t force, 455251881Speter svn_boolean_t dry_run, 456251881Speter svn_boolean_t keep_local, 457251881Speter svn_wc_notify_func2_t notify_func, 458251881Speter void *notify_baton, 459251881Speter svn_client_ctx_t *ctx, 460251881Speter apr_pool_t *pool) 461251881Speter{ 462251881Speter int i; 463251881Speter svn_boolean_t has_non_missing = FALSE; 464251881Speter 465251881Speter for (i = 0; i < targets->nelts; i++) 466251881Speter { 467251881Speter const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *); 468251881Speter 469251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 470251881Speter 471251881Speter SVN_ERR(check_external(local_abspath, ctx, pool)); 472251881Speter 473251881Speter if (!force && !keep_local) 474251881Speter { 475251881Speter svn_boolean_t missing; 476251881Speter /* Verify that there are no "awkward" files */ 477251881Speter 478251881Speter SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool)); 479251881Speter 480251881Speter if (! missing) 481251881Speter has_non_missing = TRUE; 482251881Speter } 483251881Speter else 484251881Speter has_non_missing = TRUE; 485251881Speter } 486251881Speter 487251881Speter if (!dry_run) 488251881Speter { 489251881Speter /* Mark the entry for commit deletion and perform wc deletion */ 490251881Speter 491251881Speter /* If none of the targets exists, pass keep local TRUE, to avoid 492251881Speter deleting case-different files. Detecting this in the generic case 493251881Speter from the delete code is expensive */ 494251881Speter return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets, 495251881Speter keep_local || !has_non_missing, 496251881Speter TRUE /* delete_unversioned_target */, 497251881Speter ctx->cancel_func, 498251881Speter ctx->cancel_baton, 499251881Speter notify_func, notify_baton, 500251881Speter pool)); 501251881Speter } 502251881Speter 503251881Speter return SVN_NO_ERROR; 504251881Speter} 505251881Speter 506251881Spetersvn_error_t * 507251881Spetersvn_client_delete4(const apr_array_header_t *paths, 508251881Speter svn_boolean_t force, 509251881Speter svn_boolean_t keep_local, 510251881Speter const apr_hash_t *revprop_table, 511251881Speter svn_commit_callback2_t commit_callback, 512251881Speter void *commit_baton, 513251881Speter svn_client_ctx_t *ctx, 514251881Speter apr_pool_t *pool) 515251881Speter{ 516251881Speter svn_boolean_t is_url; 517251881Speter 518251881Speter if (! paths->nelts) 519251881Speter return SVN_NO_ERROR; 520251881Speter 521251881Speter SVN_ERR(svn_client__assert_homogeneous_target_type(paths)); 522251881Speter is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *)); 523251881Speter 524251881Speter if (is_url) 525251881Speter { 526251881Speter SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback, 527251881Speter commit_baton, ctx, pool)); 528251881Speter } 529251881Speter else 530251881Speter { 531251881Speter const char *local_abspath; 532251881Speter apr_hash_t *wcroots; 533251881Speter apr_hash_index_t *hi; 534251881Speter int i; 535251881Speter int j; 536251881Speter apr_pool_t *iterpool; 537251881Speter svn_boolean_t is_new_target; 538251881Speter 539251881Speter /* Build a map of wcroots and targets within them. */ 540251881Speter wcroots = apr_hash_make(pool); 541251881Speter iterpool = svn_pool_create(pool); 542251881Speter for (i = 0; i < paths->nelts; i++) 543251881Speter { 544251881Speter const char *wcroot_abspath; 545251881Speter apr_array_header_t *targets; 546251881Speter 547251881Speter svn_pool_clear(iterpool); 548251881Speter 549251881Speter /* See if the user wants us to stop. */ 550251881Speter if (ctx->cancel_func) 551251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 552251881Speter 553251881Speter SVN_ERR(svn_dirent_get_absolute(&local_abspath, 554251881Speter APR_ARRAY_IDX(paths, i, 555251881Speter const char *), 556251881Speter pool)); 557251881Speter SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 558251881Speter local_abspath, pool, iterpool)); 559251881Speter targets = svn_hash_gets(wcroots, wcroot_abspath); 560251881Speter if (targets == NULL) 561251881Speter { 562251881Speter targets = apr_array_make(pool, 1, sizeof(const char *)); 563251881Speter svn_hash_sets(wcroots, wcroot_abspath, targets); 564251881Speter } 565251881Speter 566251881Speter /* Make sure targets are unique. */ 567251881Speter is_new_target = TRUE; 568251881Speter for (j = 0; j < targets->nelts; j++) 569251881Speter { 570251881Speter if (strcmp(APR_ARRAY_IDX(targets, j, const char *), 571251881Speter local_abspath) == 0) 572251881Speter { 573251881Speter is_new_target = FALSE; 574251881Speter break; 575251881Speter } 576251881Speter } 577251881Speter 578251881Speter if (is_new_target) 579251881Speter APR_ARRAY_PUSH(targets, const char *) = local_abspath; 580251881Speter } 581251881Speter 582251881Speter /* Delete the targets from each working copy in turn. */ 583251881Speter for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi)) 584251881Speter { 585251881Speter const char *root_abspath; 586289180Speter const apr_array_header_t *targets = apr_hash_this_val(hi); 587251881Speter 588251881Speter svn_pool_clear(iterpool); 589251881Speter 590251881Speter SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets, 591251881Speter FALSE, iterpool, iterpool)); 592251881Speter 593251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 594251881Speter svn_client__wc_delete_many(targets, force, FALSE, keep_local, 595251881Speter ctx->notify_func2, ctx->notify_baton2, 596251881Speter ctx, iterpool), 597251881Speter ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */, 598251881Speter iterpool); 599251881Speter } 600251881Speter svn_pool_destroy(iterpool); 601251881Speter } 602251881Speter 603251881Speter return SVN_NO_ERROR; 604251881Speter} 605